# -*- coding: utf-8 -*-

# Kiro Gateway
# https://github.com/jwadow/kiro-gateway
# Copyright (C) 2025 Jwadow
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.

import json
import os
import sqlite3
import sys
import uuid
from pathlib import Path
from enum import Enum

import httpx
from dotenv import load_dotenv
from loguru import logger

# --- Load environment variables ---
load_dotenv()


class AuthType(Enum):
    """Type of authentication mechanism."""
    KIRO_DESKTOP = "kiro_desktop"
    AWS_SSO_OIDC = "aws_sso_oidc"


# --- Configuration ---
# API region - CodeWhisperer API is only available in us-east-1
API_REGION = "us-east-1"
KIRO_API_HOST = f"https://q.{API_REGION}.amazonaws.com"
KIRO_DESKTOP_TOKEN_URL = f"https://prod.{API_REGION}.auth.desktop.kiro.dev/refreshToken"

# SSO region - may differ from API region (e.g., ap-southeast-1 for Singapore users)
# This is used only for AWS SSO OIDC token refresh
SSO_REGION = None
AWS_SSO_OIDC_TOKEN_URL = None  # Will be set when SSO_REGION is known

REFRESH_TOKEN = os.getenv("REFRESH_TOKEN")
PROFILE_ARN = os.getenv("PROFILE_ARN", "arn:aws:codewhisperer:us-east-1:699475941385:profile/EHGA3GRVQMUK")
KIRO_CREDS_FILE = os.getenv("KIRO_CREDS_FILE", "")
KIRO_CLI_DB_FILE = os.getenv("KIRO_CLI_DB_FILE", "")

# AWS SSO OIDC specific credentials
CLIENT_ID = None
CLIENT_SECRET = None
SCOPES = None
AUTH_TOKEN = None
AUTH_TYPE = AuthType.KIRO_DESKTOP


def load_credentials_from_json(file_path: str) -> bool:
    """Load credentials from JSON file."""
    global REFRESH_TOKEN, PROFILE_ARN, CLIENT_ID, CLIENT_SECRET, AUTH_TYPE
    global SSO_REGION, AWS_SSO_OIDC_TOKEN_URL
    
    try:
        creds_path = Path(file_path).expanduser()
        if not creds_path.exists():
            logger.warning(f"Credentials file not found: {file_path}")
            return False
        
        with open(creds_path, 'r', encoding='utf-8') as f:
            creds_data = json.load(f)
        
        # Load common fields
        if 'refreshToken' in creds_data:
            REFRESH_TOKEN = creds_data['refreshToken']
        if 'profileArn' in creds_data:
            PROFILE_ARN = creds_data['profileArn']
        if 'region' in creds_data:
            # Store as SSO region for OIDC token refresh only
            # IMPORTANT: CodeWhisperer API is only available in us-east-1,
            # so we don't update KIRO_API_HOST here
            SSO_REGION = creds_data['region']
            AWS_SSO_OIDC_TOKEN_URL = f"https://oidc.{SSO_REGION}.amazonaws.com/token"
            logger.debug(f"SSO region from JSON: {SSO_REGION} (API stays at {API_REGION})")
        
        # Load AWS SSO OIDC specific fields
        if 'clientId' in creds_data:
            CLIENT_ID = creds_data['clientId']
        if 'clientSecret' in creds_data:
            CLIENT_SECRET = creds_data['clientSecret']
        
        # Detect auth type
        if CLIENT_ID and CLIENT_SECRET:
            AUTH_TYPE = AuthType.AWS_SSO_OIDC
            logger.info(f"Detected auth type: AWS SSO OIDC")
        else:
            AUTH_TYPE = AuthType.KIRO_DESKTOP
            logger.info(f"Detected auth type: Kiro Desktop")
        
        logger.info(f"Credentials loaded from {file_path}")
        return True
        
    except Exception as e:
        logger.error(f"Error loading credentials from file: {e}")
        return False


def load_credentials_from_sqlite(db_path: str) -> bool:
    """Load credentials from kiro-cli SQLite database."""
    global REFRESH_TOKEN, CLIENT_ID, CLIENT_SECRET, AUTH_TYPE, SCOPES, AUTH_TOKEN
    global SSO_REGION, AWS_SSO_OIDC_TOKEN_URL
    
    try:
        path = Path(db_path).expanduser()
        if not path.exists():
            logger.warning(f"SQLite database not found: {db_path}")
            return False
        
        conn = sqlite3.connect(str(path))
        cursor = conn.cursor()
        
        # Load token data (try both kiro-cli and codewhisperer key formats)
        cursor.execute("SELECT value FROM auth_kv WHERE key = ?", ("kirocli:odic:token",))
        token_row = cursor.fetchone()
        if not token_row:
            cursor.execute("SELECT value FROM auth_kv WHERE key = ?", ("codewhisperer:odic:token",))
            token_row = cursor.fetchone()
        
        if token_row:
            token_data = json.loads(token_row[0])
            if token_data:
                # Check if we have a valid access token
                if 'access_token' in token_data and 'expires_at' in token_data:
                    from datetime import datetime
                    expires_at = datetime.fromisoformat(token_data['expires_at'].replace('Z', '+00:00'))
                    if expires_at > datetime.now().astimezone():
                        AUTH_TOKEN = token_data['access_token']
                        logger.info("Found valid access token in database (will use after HEADERS init)")
                if 'refresh_token' in token_data:
                    REFRESH_TOKEN = token_data['refresh_token']
                if 'scopes' in token_data:
                    SCOPES = token_data['scopes']
                if 'region' in token_data:
                    # Store as SSO region for OIDC token refresh only
                    # IMPORTANT: CodeWhisperer API is only available in us-east-1,
                    # so we don't update KIRO_API_HOST here
                    SSO_REGION = token_data['region']
                    AWS_SSO_OIDC_TOKEN_URL = f"https://oidc.{SSO_REGION}.amazonaws.com/token"
                    logger.debug(f"SSO region from SQLite: {SSO_REGION} (API stays at {API_REGION})")
        
        # Load device registration (client_id, client_secret) - try both key formats
        cursor.execute("SELECT value FROM auth_kv WHERE key = ?", ("kirocli:odic:device-registration",))
        registration_row = cursor.fetchone()
        if not registration_row:
            cursor.execute("SELECT value FROM auth_kv WHERE key = ?", ("codewhisperer:odic:device-registration",))
            registration_row = cursor.fetchone()
        
        if registration_row:
            registration_data = json.loads(registration_row[0])
            if registration_data:
                if 'client_id' in registration_data:
                    CLIENT_ID = registration_data['client_id']
                if 'client_secret' in registration_data:
                    CLIENT_SECRET = registration_data['client_secret']
        
        conn.close()
        
        # Detect auth type
        if CLIENT_ID and CLIENT_SECRET:
            AUTH_TYPE = AuthType.AWS_SSO_OIDC
            logger.info(f"Detected auth type: AWS SSO OIDC (from SQLite)")
        else:
            AUTH_TYPE = AuthType.KIRO_DESKTOP
            logger.info(f"Detected auth type: Kiro Desktop (from SQLite)")
        
        logger.info(f"Credentials loaded from SQLite: {db_path}")
        return True
        
    except sqlite3.Error as e:
        logger.error(f"SQLite error: {e}")
        return False
    except Exception as e:
        logger.error(f"Error loading credentials from SQLite: {e}")
        return False


# --- Load credentials (priority: SQLite > JSON > env) ---
cred_source = "REFRESH_TOKEN"

if KIRO_CLI_DB_FILE:
    if load_credentials_from_sqlite(KIRO_CLI_DB_FILE):
        cred_source = "KIRO_CLI_DB_FILE (SQLite)"
elif KIRO_CREDS_FILE:
    if load_credentials_from_json(KIRO_CREDS_FILE):
        cred_source = "KIRO_CREDS_FILE (JSON)"

# --- Validate required credentials ---
if not REFRESH_TOKEN:
    logger.error("No credentials configured. Set REFRESH_TOKEN, KIRO_CREDS_FILE, or KIRO_CLI_DB_FILE. Exiting.")
    sys.exit(1)

# Additional validation for AWS SSO OIDC
if AUTH_TYPE == AuthType.AWS_SSO_OIDC and (not CLIENT_ID or not CLIENT_SECRET):
    logger.error("AWS SSO OIDC requires clientId and clientSecret. Exiting.")
    sys.exit(1)

# Global variables
AUTH_TOKEN = None
HEADERS = {
    "Authorization": None,
    "Content-Type": "application/json",
    "User-Agent": "aws-sdk-js/1.0.27 ua/2.1 os/win32#10.0.19044 lang/js md/nodejs#22.21.1 api/codewhispererstreaming#1.0.27 m/E KiroIDE-0.7.45-31c325a0ff0a9c8dec5d13048f4257462d751fe5b8af4cb1088f1fca45856c64",
    "x-amz-user-agent": "aws-sdk-js/1.0.27 KiroIDE-0.7.45-31c325a0ff0a9c8dec5d13048f4257462d751fe5b8af4cb1088f1fca45856c64",
    "x-amzn-codewhisperer-optout": "true",
    "x-amzn-kiro-agent-mode": "vibe",
}


def refresh_auth_token():
    """Refreshes AUTH_TOKEN via appropriate endpoint based on auth type."""
    global AUTH_TOKEN, HEADERS
    
    if AUTH_TYPE == AuthType.AWS_SSO_OIDC:
        return refresh_auth_token_aws_sso_oidc()
    else:
        return refresh_auth_token_kiro_desktop()


def refresh_auth_token_kiro_desktop():
    """Refreshes AUTH_TOKEN via Kiro Desktop Auth endpoint."""
    global AUTH_TOKEN, HEADERS
    logger.info("Refreshing Kiro token via Kiro Desktop Auth...")
    
    payload = {"refreshToken": REFRESH_TOKEN}
    headers = {
        "Content-Type": "application/json",
        "User-Agent": "KiroIDE-0.7.45-31c325a0ff0a9c8dec5d13048f4257462d751fe5b8af4cb1088f1fca45856c64",
    }
    
    try:
        response = httpx.post(KIRO_DESKTOP_TOKEN_URL, json=payload, headers=headers)
        response.raise_for_status()
        data = response.json()
        
        new_token = data.get("accessToken")
        expires_in = data.get("expiresIn")
        
        if not new_token:
            logger.error("Failed to get accessToken from response")
            return False

        logger.success(f"Token refreshed via Kiro Desktop Auth. Expires in: {expires_in}s")
        AUTH_TOKEN = new_token
        HEADERS['Authorization'] = f"Bearer {AUTH_TOKEN}"
        return True
        
    except httpx.HTTPError as e:
        logger.error(f"Error refreshing token via Kiro Desktop Auth: {e}")
        if hasattr(e, 'response') and e.response:
            logger.error(f"Server response: {e.response.status_code} {e.response.text}")
        return False


def refresh_auth_token_aws_sso_oidc():
    """Refreshes AUTH_TOKEN via AWS SSO OIDC endpoint."""
    global AUTH_TOKEN, HEADERS
    logger.info("Refreshing Kiro token via AWS SSO OIDC...")
    
    # Determine SSO OIDC URL (use SSO_REGION if set, otherwise fall back to API_REGION)
    sso_region = SSO_REGION or API_REGION
    oidc_url = AWS_SSO_OIDC_TOKEN_URL or f"https://oidc.{sso_region}.amazonaws.com/token"
    
    # AWS SSO OIDC uses form-urlencoded data
    data = {
        "grant_type": "refresh_token",
        "client_id": CLIENT_ID,
        "client_secret": CLIENT_SECRET,
        "refresh_token": REFRESH_TOKEN,
    }
    
    # Note: scope parameter is NOT sent during refresh per OAuth 2.0 RFC 6749 Section 6
    # AWS SSO OIDC uses the originally granted scopes automatically
    headers = {
        "Content-Type": "application/x-www-form-urlencoded",
    }
    
    # Log request details (without secrets) for debugging
    logger.debug(f"AWS SSO OIDC refresh request: url={oidc_url}, "
                 f"sso_region={sso_region}, api_region={API_REGION}, "
                 f"client_id={CLIENT_ID[:8] if CLIENT_ID else 'None'}...")
    
    try:
        response = httpx.post(oidc_url, data=data, headers=headers)
        
        # Log response details for debugging (especially on errors)
        if response.status_code != 200:
            logger.error(f"AWS SSO OIDC refresh failed: status={response.status_code}")
            logger.error(f"AWS SSO OIDC response body: {response.text}")
            # Try to parse AWS error for more details
            try:
                error_json = response.json()
                error_code = error_json.get("error", "unknown")
                error_desc = error_json.get("error_description", "no description")
                logger.error(f"AWS SSO OIDC error details: error={error_code}, "
                             f"description={error_desc}")
            except Exception:
                pass  # Body wasn't JSON, already logged as text
            response.raise_for_status()
        
        result = response.json()
        
        new_token = result.get("accessToken")
        expires_in = result.get("expiresIn", 3600)
        
        if not new_token:
            logger.error(f"Failed to get accessToken from AWS SSO OIDC response: {result}")
            return False

        logger.success(f"Token refreshed via AWS SSO OIDC. Expires in: {expires_in}s")
        AUTH_TOKEN = new_token
        HEADERS['Authorization'] = f"Bearer {AUTH_TOKEN}"
        return True
        
    except httpx.HTTPError as e:
        logger.error(f"Error refreshing token via AWS SSO OIDC: {e}")
        if hasattr(e, 'response') and e.response is not None:
            logger.error(f"Server response: {e.response.status_code} {e.response.text}")
        return False


def get_profile_arn():
    """Gets the profile ARN from ListAvailableProfiles endpoint."""
    global PROFILE_ARN
    logger.info("Getting profile ARN from /ListAvailableProfiles...")
    url = f"{KIRO_API_HOST}/ListAvailableProfiles"
    
    try:
        response = httpx.get(url, headers=HEADERS)
        response.raise_for_status()
        data = response.json()
        
        profiles = data.get("profiles", [])
        if profiles:
            # Use the first available profile
            PROFILE_ARN = profiles[0].get("arn")
            logger.info(f"Found profile ARN: {PROFILE_ARN}")
            return True
        else:
            logger.warning("No profiles found")
            return False
    except httpx.HTTPError as e:
        logger.error(f"ListAvailableProfiles failed: {e}")
        if hasattr(e, 'response') and e.response is not None:
            logger.error(f"Server response: {e.response.status_code} {e.response.text}")
        return False


def test_get_models():
    """Tests the ListAvailableModels endpoint."""
    logger.info("Testing /ListAvailableModels...")
    url = f"{KIRO_API_HOST}/ListAvailableModels"
    params = {
        "origin": "AI_EDITOR",
        "profileArn": PROFILE_ARN
    }

    try:
        response = httpx.get(url, headers=HEADERS, params=params)
        response.raise_for_status()

        logger.info(f"Response status: {response.status_code}")
        logger.debug(f"Response (JSON):\n{json.dumps(response.json(), indent=2, ensure_ascii=False)}")
        logger.success("ListAvailableModels test COMPLETED SUCCESSFULLY")
        return True
    except httpx.HTTPError as e:
        logger.error(f"ListAvailableModels test failed: {e}")
        return False


def test_generate_content():
    """Tests the generateAssistantResponse endpoint."""
    logger.info("Testing /generateAssistantResponse...")
    url = f"{KIRO_API_HOST}/generateAssistantResponse"
    
    payload = {
        "conversationState": {
            "agentContinuationId": str(uuid.uuid4()),
            "agentTaskType": "vibe",
            "chatTriggerType": "MANUAL",
            "conversationId": str(uuid.uuid4()),
            "currentMessage": {
                "userInputMessage": {
                    "content": "Hello! Say something short.",
                    "modelId": "claude-haiku-4.5",
                    "origin": "AI_EDITOR",
                    "userInputMessageContext": {
                        "tools": []
                    }
                }
            },
            "history": []
        }
    }
    
    # Only add profileArn if it's set and not AWS SSO OIDC
    # AWS SSO OIDC (Builder ID) users don't need profileArn and it causes 403 if sent
    if PROFILE_ARN and AUTH_TYPE != AuthType.AWS_SSO_OIDC:
        payload["profileArn"] = PROFILE_ARN

    try:
        with httpx.stream("POST", url, headers=HEADERS, json=payload) as response:
            response.raise_for_status()
            logger.info(f"Response status: {response.status_code}")
            logger.info("Streaming response:")

            for chunk in response.iter_bytes(chunk_size=1024):
                if chunk:
                    # Try to decode and find JSON
                    chunk_str = chunk.decode('utf-8', errors='ignore')
                    logger.debug(f"Chunk: {chunk_str[:200]}...")

        logger.success("generateAssistantResponse test COMPLETED")
        return True
    except httpx.HTTPError as e:
        logger.error(f"generateAssistantResponse test failed: {e}")
        return False


if __name__ == "__main__":
    logger.info(f"Starting Kiro API tests...")
    logger.info(f"  Credentials source: {cred_source}")
    logger.info(f"  Auth type: {AUTH_TYPE.value}")
    logger.info(f"  API Region: {API_REGION}")
    logger.info(f"  SSO Region: {SSO_REGION or 'not set (using API region)'}")
    logger.info(f"  API Host: {KIRO_API_HOST}")

    # Check if we already have a valid token from the database
    if AUTH_TOKEN:
        HEADERS['Authorization'] = f"Bearer {AUTH_TOKEN}"
        logger.info("Using existing valid access token from database")
        token_ok = True
    else:
        token_ok = refresh_auth_token()

    if token_ok:
        # Get profile ARN dynamically for AWS SSO OIDC users
        if AUTH_TYPE == AuthType.AWS_SSO_OIDC:
            get_profile_arn()
        
        models_ok = test_get_models()
        generate_ok = test_generate_content()

        if models_ok and generate_ok:
            logger.success(f"All tests passed successfully!")
            logger.success(f"  Auth type: {AUTH_TYPE.value}")
            logger.success(f"  Credentials: {cred_source}")
        else:
            logger.warning(f"One or more tests failed.")
    else:
        logger.error("Failed to refresh token. Tests not started.")
        logger.error(f"  Auth type: {AUTH_TYPE.value}")
        sso_region = SSO_REGION or API_REGION
        oidc_url = AWS_SSO_OIDC_TOKEN_URL or f"https://oidc.{sso_region}.amazonaws.com/token"
        logger.error(f"  Token URL: {oidc_url if AUTH_TYPE == AuthType.AWS_SSO_OIDC else KIRO_DESKTOP_TOKEN_URL}")
