use kodegen_mcp_tool::{McpError, Tool, ToolExecutionContext, ToolResponse};
use kodegen_mcp_schema::github::{
    GetFileContentsArgs, 
    GitHubGetFileContentsOutput,
    GitHubFileContent,
    GitHubDirectoryEntry,
    GITHUB_GET_FILE_CONTENTS
};
use rmcp::model::{PromptArgument, PromptMessage, PromptMessageRole, PromptMessageContent};
use anyhow;

use crate::GitHubClient;

/// Tool for getting file or directory contents from a GitHub repository
pub struct GetFileContentsTool;

impl Tool for GetFileContentsTool {
    type Args = GetFileContentsArgs;
    type PromptArgs = ();

    fn name() -> &'static str {
        GITHUB_GET_FILE_CONTENTS
    }

    fn description() -> &'static str {
        "Get file or directory contents from a GitHub repository"
    }

    fn read_only() -> bool {
        true
    }

    fn destructive() -> bool {
        false
    }

    fn idempotent() -> bool {
        true
    }

    fn open_world() -> bool {
        true
    }

    async fn execute(&self, args: Self::Args, _ctx: ToolExecutionContext) 
        -> Result<ToolResponse<<Self::Args as kodegen_mcp_schema::ToolArgs>::Output>, McpError>
    {
        let token = std::env::var("GITHUB_TOKEN")
            .map_err(|_| McpError::Other(anyhow::anyhow!(
                "GITHUB_TOKEN environment variable not set"
            )))?;

        let client = GitHubClient::builder()
            .personal_token(token)
            .build()
            .map_err(|e| McpError::Other(anyhow::anyhow!("Failed to create GitHub client: {}", e)))?;

        let task_result = client
            .get_file_contents(
                args.owner.clone(),
                args.repo.clone(),
                args.path.clone(),
                args.ref_name.clone(),
            )
            .await;

        let api_result = task_result
            .map_err(|e| McpError::Other(anyhow::anyhow!("Task channel error: {}", e)))?;

        let content_vec = api_result
            .map_err(|e| McpError::Other(anyhow::anyhow!("GitHub API error: {}", e)))?;

        // Determine if file or directory based on response structure
        if content_vec.len() == 1 && content_vec[0].r#type == "file" {
            // SINGLE FILE CASE
            let file = &content_vec[0];
            
            // Decode base64 content
            let content = file.decoded_content().unwrap_or_default();
            
            // Build display
            let content_preview = if content.len() > 500 {
                format!("{}...\n\n(Content truncated - {} bytes total)", 
                    &content[..500], content.len())
            } else {
                content.clone()
            };
            
            let display = format!(
                "📄 File: {}\n\
                 Repository: {}/{}\n\
                 Ref: {}\n\
                 Size: {} bytes\n\
                 SHA: {}\n\n\
                 Content:\n\
                 {}",
                args.path,
                args.owner,
                args.repo,
                args.ref_name.as_deref().unwrap_or("default branch"),
                file.size,
                &file.sha[..7],
                content_preview
            );
            
            // Build typed output
            let output = GitHubGetFileContentsOutput {
                success: true,
                owner: args.owner,
                repo: args.repo,
                path: args.path,
                ref_name: args.ref_name,
                content_type: "file".to_string(),
                file_content: Some(GitHubFileContent {
                    name: file.name.clone(),
                    path: file.path.clone(),
                    sha: file.sha.clone(),
                    size: file.size as u64,
                    content,
                    encoding: file.encoding.clone().unwrap_or_default(),
                    html_url: file.html_url.clone().unwrap_or_default(),
                    git_url: file.git_url.clone().unwrap_or_default(),
                    download_url: file.download_url.clone(),
                }),
                directory_contents: None,
            };
            
            Ok(ToolResponse::new(display, output))
            
        } else {
            // DIRECTORY CASE (multiple items)
            let entries: Vec<GitHubDirectoryEntry> = content_vec.iter().map(|entry| {
                GitHubDirectoryEntry {
                    name: entry.name.clone(),
                    path: entry.path.clone(),
                    sha: entry.sha.clone(),
                    size: entry.size as u64,
                    entry_type: entry.r#type.clone(),
                    html_url: entry.html_url.clone().unwrap_or_default(),
                }
            }).collect();
            
            // Build display
            let items_preview = entries.iter()
                .take(20)
                .map(|e| {
                    let icon = match e.entry_type.as_str() {
                        "dir" => "📁",
                        "file" => "📄",
                        _ => "🔗"
                    };
                    format!("  {} {}", icon, e.name)
                })
                .collect::<Vec<_>>()
                .join("\n");
            
            let more_indicator = if entries.len() > 20 {
                format!("\n  ... and {} more items", entries.len() - 20)
            } else {
                String::new()
            };
            
            let display = format!(
                "📁 Directory: {}\n\
                 Repository: {}/{}\n\
                 Ref: {}\n\
                 Total Items: {}\n\n\
                 Contents:\n\
                 {}{}",
                args.path,
                args.owner,
                args.repo,
                args.ref_name.as_deref().unwrap_or("default branch"),
                entries.len(),
                items_preview,
                more_indicator
            );
            
            // Build typed output
            let output = GitHubGetFileContentsOutput {
                success: true,
                owner: args.owner,
                repo: args.repo,
                path: args.path,
                ref_name: args.ref_name,
                content_type: "directory".to_string(),
                file_content: None,
                directory_contents: Some(entries),
            };
            
            Ok(ToolResponse::new(display, output))
        }
    }

    async fn prompt(&self, _args: Self::PromptArgs) -> Result<Vec<PromptMessage>, McpError> {
        Ok(vec![PromptMessage {
            role: PromptMessageRole::User,
            content: PromptMessageContent::text(
                r#"# GitHub File Contents Examples

## Get a Single File
To retrieve the contents of a file from a repository:

```json
{
  "owner": "octocat",
  "repo": "hello-world",
  "path": "README.md"
}
```

## Get File from Specific Branch
To get a file from a specific branch, tag, or commit:

```json
{
  "owner": "octocat",
  "repo": "hello-world",
  "path": "src/main.rs",
  "ref_name": "develop"
}
```

## Get File from Specific Commit
To retrieve a file at a specific commit SHA:

```json
{
  "owner": "octocat",
  "repo": "hello-world",
  "path": "config.json",
  "ref_name": "a1b2c3d4e5f6"
}
```

## List Directory Contents
To list all files and subdirectories in a directory:

```json
{
  "owner": "octocat",
  "repo": "hello-world",
  "path": "src"
}
```

## Response Information

For **files**, the response includes:
- **name**: File name
- **path**: Full path in repository
- **type**: "file"
- **content**: Base64-encoded file content
- **size**: File size in bytes
- **sha**: Git blob SHA
- **download_url**: Direct download URL
- **html_url**: GitHub web URL

For **directories**, the response is an array of items, each with:
- **name**: Item name
- **path**: Full path
- **type**: "file" or "dir"
- **size**: Size (for files)
- **sha**: Git SHA
- **html_url**: GitHub web URL

## Decoding File Content

File content is base64-encoded. To decode:

**JavaScript:**
```javascript
const content = Buffer.from(base64Content, 'base64').toString('utf-8');
```

**Python:**
```python
import base64
content = base64.b64decode(base64_content).decode('utf-8')
```

**Rust:**
```rust
use base64::{Engine as _, engine::general_purpose};
let content = general_purpose::STANDARD.decode(base64_content)?;
let text = String::from_utf8(content)?;
```

## Common Use Cases

1. **Read Configuration**: Get config files for analysis
2. **Code Review**: Fetch source files for review
3. **Documentation**: Retrieve README and docs
4. **Directory Browsing**: Navigate repository structure
5. **Content Analysis**: Analyze file contents programmatically
6. **Historical Versions**: Get files from specific commits

## Best Practices

- Use `ref_name` to ensure you're reading from the correct branch
- For directories, the API returns all items (may be limited for very large directories)
- Check the `type` field to distinguish files from directories
- Cache frequently accessed files to reduce API calls
- Use `sha` to detect if file has changed between requests

## Error Handling

- **404**: File or directory not found at the specified path
- **403**: Access denied (private repository, no permissions)
- **401**: Invalid or missing GITHUB_TOKEN
- **422**: Invalid reference name
"#,
            ),
        }])
    }

    fn prompt_arguments() -> Vec<PromptArgument> {
        vec![PromptArgument {
            name: "reference_type".to_string(),
            title: None,
            description: Some(
                "Optional reference type to emphasize in examples (e.g., 'branches', 'tags', 'commits')"
                    .to_string(),
            ),
            required: Some(false),
        }]
    }
}
