#!/usr/bin/env python3
"""
ArtifyBot — Etsy Product Automation Framework
Secure, modular version with proper credential management.
"""

import os
import json
import logging
import tempfile
from pathlib import Path
from typing import Optional, Dict, List
from dataclasses import dataclass
import hashlib

# Third-party imports (optional features)
try:
    import requests
except ImportError:
    requests = None

try:
    from PIL import Image
    import cv2
except ImportError:
    Image = None
    cv2 = None

try:
    from googleapiclient.discovery import build
    from google.oauth2.credentials import Credentials as GoogleCredentials
except ImportError:
    build = None
    GoogleCredentials = None

try:
    from selenium import webdriver
    from selenium.webdriver.chrome.options import Options
except ImportError:
    webdriver = None
    Options = None

try:
    import openai
except ImportError:
    openai = None


# ============================================================================
# Configuration & Secrets Management
# ============================================================================

@dataclass
class ArtifyConfig:
    """Configuration for ArtifyBot."""
    
    # Directories
    base_dir: Path = Path(os.path.expanduser("~/Documents/ArtifyBot"))
    work_dir: Path = Path(tempfile.gettempdir())
    output_dir: Path = Path(os.path.expanduser("~/Documents/ArtifyBot/output"))
    
    # API Keys (loaded from .env or config)
    openai_key: Optional[str] = None
    discord_token: Optional[str] = None
    etsy_api_key: Optional[str] = None
    etsy_shop_id: Optional[str] = None
    google_drive_creds_file: Optional[str] = None
    
    # Processing options
    image_dpi: int = 300
    image_width_inches: int = 20
    image_height_inches: int = 30
    
    def __post_init__(self):
        """Create directories and load credentials."""
        self.base_dir.mkdir(parents=True, exist_ok=True)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        self.load_from_env()
        
    def load_from_env(self):
        """Load credentials from .env file."""
        env_path = self.base_dir / ".env"
        if env_path.exists():
            with open(env_path) as f:
                for line in f:
                    if '=' in line and not line.strip().startswith('#'):
                        key, val = line.strip().split('=', 1)
                        setattr(self, key.lower(), val)


# ============================================================================
# Logging
# ============================================================================

logger = logging.getLogger(__name__)
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
logger.setLevel(logging.INFO)


# ============================================================================
# Image Processing
# ============================================================================

class ImageProcessor:
    """Handle image conversions, cropping, resizing."""
    
    @staticmethod
    def convert_webp_to_png(webp_path: str) -> str:
        """Convert WebP to PNG."""
        if not Image:
            raise ImportError("Pillow required for image conversion")
            
        webp_path = Path(webp_path)
        png_path = webp_path.with_suffix('.png')
        
        try:
            with Image.open(webp_path) as img:
                img.convert('RGB').save(png_path, 'PNG')
            logger.info(f"Converted {webp_path.name} → {png_path.name}")
            return str(png_path)
        except Exception as e:
            logger.error(f"Conversion failed: {e}")
            raise
    
    @staticmethod
    def crop_to_ratio(image_path: str, width: float, height: float, 
                      aspect_name: str = "") -> str:
        """Crop image to specific aspect ratio."""
        if not cv2:
            raise ImportError("opencv-python required for image cropping")
            
        try:
            img = cv2.imread(image_path)
            if img is None:
                raise FileNotFoundError(f"Cannot read image: {image_path}")
            
            # Calculate target dimensions based on DPI and inches
            dpi = 300
            target_height_px = dpi * height / img.shape[0]
            target_width_px = dpi * width / img.shape[1]
            img = cv2.resize(img, (0, 0), fx=target_width_px, fy=target_height_px)
            
            # Crop to aspect ratio
            img_h, img_w = img.shape[:2]
            target_ratio = width / height
            img_ratio = img_w / img_h
            
            if target_ratio > img_ratio:
                crop_w = img_w
                crop_h = int(img_w / target_ratio)
            else:
                crop_h = img_h
                crop_w = int(img_h * target_ratio)
            
            start_x = (img_w - crop_w) // 2
            start_y = (img_h - crop_h) // 2
            cropped = img[start_y:start_y+crop_h, start_x:start_x+crop_w]
            
            # Save with aspect suffix
            output_path = Path(image_path)
            suffix = f"_{aspect_name}" if aspect_name else ""
            out = output_path.with_stem(output_path.stem + suffix)
            
            cv2.imwrite(str(out), cropped, [cv2.IMWRITE_JPEG_QUALITY, 100])
            logger.info(f"Cropped {output_path.name} to {width}×{height} aspect")
            return str(out)
        except Exception as e:
            logger.error(f"Cropping failed: {e}")
            raise


# ============================================================================
# Discord / MidJourney Integration
# ============================================================================

class DiscordIntegration:
    """Discord API for MidJourney prompt automation."""
    
    def __init__(self, token: str):
        if not requests:
            raise ImportError("requests required for Discord integration")
        self.token = token
        self.base_url = "https://discord.com/api/v9"
    
    def send_prompt(self, channel_id: str, prompt: str) -> bool:
        """Send /imagine prompt to Discord channel."""
        try:
            headers = {'authorization': self.token}
            payload = {'content': f'/imagine prompt {prompt}'}
            resp = requests.post(
                f'{self.base_url}/channels/{channel_id}/messages',
                json=payload,
                headers=headers,
                timeout=10
            )
            if resp.status_code == 200:
                logger.info(f"Sent prompt to Discord channel {channel_id}")
                return True
            else:
                logger.error(f"Discord error: {resp.status_code} - {resp.text}")
                return False
        except Exception as e:
            logger.error(f"Failed to send prompt: {e}")
            return False
    
    def fetch_latest_image(self, channel_id: str) -> Optional[str]:
        """Download latest image attachment from channel."""
        try:
            headers = {'authorization': self.token}
            resp = requests.get(
                f'{self.base_url}/channels/{channel_id}/messages',
                headers=headers,
                timeout=10
            )
            if resp.status_code != 200:
                logger.error(f"Discord error: {resp.status_code}")
                return None
            
            messages = resp.json()
            for msg in messages:
                if msg.get('attachments'):
                    attachment = msg['attachments'][0]
                    url = attachment['url']
                    filename = attachment.get('filename', 'image')
                    
                    # Download image
                    img_resp = requests.get(url, timeout=10)
                    if img_resp.status_code == 200:
                        output = Path(tempfile.gettempdir()) / filename
                        output.write_bytes(img_resp.content)
                        logger.info(f"Downloaded image: {filename}")
                        return str(output)
            
            logger.warning(f"No attachments found in channel {channel_id}")
            return None
        except Exception as e:
            logger.error(f"Failed to fetch image: {e}")
            return None


# ============================================================================
# Google Drive Integration
# ============================================================================

class GoogleDriveIntegration:
    """Google Drive API for file management."""
    
    def __init__(self, credentials_file: str):
        if not build or not GoogleCredentials:
            raise ImportError("google-api-python-client required")
        
        cred_path = Path(credentials_file)
        if not cred_path.exists():
            raise FileNotFoundError(f"Credentials file not found: {credentials_file}")
        
        with open(cred_path) as f:
            cred_info = json.load(f)
        
        self.creds = GoogleCredentials.from_authorized_user_info(
            cred_info,
            ['https://www.googleapis.com/auth/drive']
        )
        self.service = build('drive', 'v3', credentials=self.creds)
    
    def create_folder(self, folder_name: str, parent_id: Optional[str] = None) -> str:
        """Create folder in Google Drive."""
        try:
            metadata = {
                'name': folder_name,
                'mimeType': 'application/vnd.google-apps.folder'
            }
            if parent_id:
                metadata['parents'] = [parent_id]
            
            folder = self.service.files().create(body=metadata, fields='id').execute()
            folder_id = folder.get('id')
            logger.info(f"Created folder: {folder_name} ({folder_id})")
            return folder_id
        except Exception as e:
            logger.error(f"Failed to create folder: {e}")
            raise
    
    def upload_file(self, file_path: str, parent_id: str) -> str:
        """Upload file to Google Drive."""
        try:
            from googleapiclient.http import MediaFileUpload
            
            file_path = Path(file_path)
            metadata = {
                'name': file_path.name,
                'parents': [parent_id]
            }
            
            media = MediaFileUpload(str(file_path), mimetype='*/*')
            file = self.service.files().create(body=metadata, media_body=media, fields='id').execute()
            logger.info(f"Uploaded: {file_path.name}")
            return file.get('id')
        except Exception as e:
            logger.error(f"Failed to upload file: {e}")
            raise


# ============================================================================
# Etsy Integration
# ============================================================================

class EtsyIntegration:
    """Etsy API for product listings."""
    
    def __init__(self, api_key: str, shop_id: str):
        if not requests:
            raise ImportError("requests required for Etsy API")
        self.api_key = api_key
        self.shop_id = shop_id
        self.base_url = "https://openapi.etsy.com/v3"
    
    def create_listing(self, title: str, description: str, price: float,
                       tags: List[str] = None) -> Dict:
        """Create new product listing on Etsy."""
        try:
            headers = {
                'Authorization': f'Bearer {self.api_key}',
                'Content-Type': 'application/json'
            }
            
            payload = {
                'title': title,
                'description': description,
                'price': price,
                'quantity': 999,  # Digital products (unlimited)
                'should_auto_renew': True,
                'tags': tags or []
            }
            
            resp = requests.post(
                f'{self.base_url}/shops/{self.shop_id}/listings',
                json=payload,
                headers=headers,
                timeout=30
            )
            
            if resp.status_code in [201, 200]:
                listing = resp.json().get('data', {})
                logger.info(f"Created Etsy listing: {title}")
                return listing
            else:
                logger.error(f"Etsy error: {resp.status_code} - {resp.text}")
                return {}
        except Exception as e:
            logger.error(f"Failed to create listing: {e}")
            raise


# ============================================================================
# OpenAI Prompt Generation
# ============================================================================

class PromptGenerator:
    """Generate creative prompts using OpenAI."""
    
    def __init__(self, api_key: str):
        if not openai:
            raise ImportError("openai required for prompt generation")
        openai.api_key = api_key
    
    def generate(self, topic: str, style: str = "digital art", 
                 count: int = 3) -> List[str]:
        """Generate creative prompts."""
        try:
            system_msg = f"""You are a creative prompt generator for digital artists.
Generate {count} unique, detailed prompts for {style} in the theme of: {topic}
Each prompt should be vivid, specific, and suitable for MidJourney/DALL-E.
Return as JSON array of strings."""
            
            response = openai.ChatCompletion.create(
                model="gpt-3.5-turbo",
                messages=[{"role": "user", "content": system_msg}],
                temperature=0.8,
                max_tokens=500
            )
            
            content = response['choices'][0]['message']['content']
            # Parse JSON response
            import re
            match = re.search(r'\[.*\]', content, re.DOTALL)
            if match:
                prompts = json.loads(match.group())
                logger.info(f"Generated {len(prompts)} prompts")
                return prompts
            else:
                logger.warning("Could not parse generated prompts")
                return [content]
        except Exception as e:
            logger.error(f"Prompt generation failed: {e}")
            raise


# ============================================================================
# Main ArtifyBot Class
# ============================================================================

class ArtifyBot:
    """Main orchestration for Etsy automation workflow."""
    
    def __init__(self, config: Optional[ArtifyConfig] = None):
        self.config = config or ArtifyConfig()
        self.discord = None
        self.google_drive = None
        self.etsy = None
        self.prompt_gen = None
        self.image_processor = ImageProcessor()
    
    def initialize(self):
        """Initialize all integrations."""
        if self.config.discord_token:
            self.discord = DiscordIntegration(self.config.discord_token)
        
        if self.config.google_drive_creds_file:
            self.google_drive = GoogleDriveIntegration(self.config.google_drive_creds_file)
        
        if self.config.etsy_api_key and self.config.etsy_shop_id:
            self.etsy = EtsyIntegration(self.config.etsy_api_key, self.config.etsy_shop_id)
        
        if self.config.openai_key:
            self.prompt_gen = PromptGenerator(self.config.openai_key)
        
        logger.info("ArtifyBot initialized")
    
    def workflow_generate_and_list(self, topic: str, price: float = 9.99):
        """Complete workflow: generate prompts → get images → list on Etsy."""
        try:
            if not self.prompt_gen:
                logger.error("OpenAI not configured")
                return False
            
            # 1. Generate prompts
            prompts = self.prompt_gen.generate(topic, count=1)
            logger.info(f"Generated prompt: {prompts[0]}")
            
            # 2. Send to MidJourney (if Discord configured)
            if self.discord:
                # Note: requires CHANNEL_ID environment variable
                self.discord.send_prompt(os.getenv('MIDJOURNEY_CHANNEL_ID', ''), prompts[0])
            
            logger.info(f"Workflow started for: {topic}")
            return True
        except Exception as e:
            logger.error(f"Workflow failed: {e}")
            return False


# ============================================================================
# CLI / Testing
# ============================================================================

if __name__ == '__main__':
    import sys
    
    # Example usage
    config = ArtifyConfig()
    bot = ArtifyBot(config)
    bot.initialize()
    
    if len(sys.argv) > 1:
        topic = ' '.join(sys.argv[1:])
        bot.workflow_generate_and_list(topic)
    else:
        print("ArtifyBot CLI - Usage:")
        print("  python artifybot.py <topic>")
        print("\nRequirements: Set environment variables in ~/.openclaw/workspace/skills/artifybot/.env")
