"""
Evolution Generators

Template generators for Docker, Frontend, CI/CD, Database, and Documentation.

Extracted from evolution_manager.py to reduce file size.
@version 2.0.0
"""

import json
from pathlib import Path
from typing import Any, Optional


class EvolutionGenerators:
    """
    Template generators for evolution pipeline.
    
    Generates additional files like Docker, Frontend, CI/CD, etc.
    """
    
    def __init__(self, contract: Optional[dict] = None, port: int = 3000):
        self._contract = contract
        self._port = port
    
    def set_contract(self, contract: dict) -> None:
        """Set contract for generation"""
        self._contract = contract
    
    async def generate_database(self, target_dir: str) -> None:
        """Generate Prisma database schema"""
        if not self._contract:
            return
        
        entities = self._contract.get("definition", {}).get("entities", [])
        
        models = ""
        for entity in entities:
            name = entity.get("name", "Item")
            fields = entity.get("fields", [])
            
            field_defs = []
            for field in fields:
                fname = field.get("name", "")
                ftype = field.get("type", "String")
                
                prisma_type = {
                    "UUID": "String @id @default(uuid())",
                    "String": "String",
                    "Int": "Int",
                    "Float": "Float",
                    "Boolean": "Boolean",
                    "DateTime": "DateTime @default(now())",
                    "Date": "DateTime",
                }.get(ftype, "String")
                
                if fname == "id":
                    field_defs.append(f"  {fname} {prisma_type}")
                elif "At" in fname:
                    field_defs.append(f"  {fname} DateTime @default(now())")
                else:
                    field_defs.append(f"  {fname} {prisma_type}")
            
            models += f"""
model {name} {{
{chr(10).join(field_defs)}
}}
"""
        
        prisma_schema = f'''// Prisma Schema
// Generated by Reclapp Evolution Engine

generator client {{
  provider = "prisma-client-js"
}}

datasource db {{
  provider = "sqlite"
  url      = env("DATABASE_URL")
}}
{models}
'''
        
        prisma_dir = Path(target_dir) / "api" / "prisma"
        prisma_dir.mkdir(parents=True, exist_ok=True)
        
        schema_path = prisma_dir / "schema.prisma"
        schema_path.write_text(prisma_schema)
        
        env_content = 'DATABASE_URL="file:./dev.db"\n'
        env_path = Path(target_dir) / "api" / ".env"
        env_path.write_text(env_content)
    
    async def generate_frontend(self, target_dir: str) -> None:
        """Generate React frontend"""
        if not self._contract:
            return
        
        entities = self._contract.get("definition", {}).get("entities", [])
        app_name = self._contract.get("definition", {}).get("app", {}).get("name", "App")
        
        frontend_dir = Path(target_dir) / "frontend"
        src_dir = frontend_dir / "src"
        src_dir.mkdir(parents=True, exist_ok=True)
        
        # Package.json
        package_json = {
            "name": f"{app_name.lower().replace(' ', '-')}-frontend",
            "version": "1.0.0",
            "type": "module",
            "scripts": {
                "dev": "vite",
                "build": "vite build",
                "preview": "vite preview"
            },
            "dependencies": {
                "react": "^18.2.0",
                "react-dom": "^18.2.0"
            },
            "devDependencies": {
                "@types/react": "^18.2.0",
                "@types/react-dom": "^18.2.0",
                "@vitejs/plugin-react": "^4.2.0",
                "typescript": "^5.3.0",
                "vite": "^5.0.0"
            }
        }
        (frontend_dir / "package.json").write_text(json.dumps(package_json, indent=2))
        
        # Vite config
        vite_config = '''import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: {
    port: 5173,
    proxy: {
      '/api': 'http://localhost:3000'
    }
  }
})
'''
        (frontend_dir / "vite.config.ts").write_text(vite_config)
        
        # Index.html
        index_html = f'''<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>{app_name}</title>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="/src/main.tsx"></script>
</body>
</html>
'''
        (frontend_dir / "index.html").write_text(index_html)
        
        # Main.tsx
        main_tsx = '''import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'
import './index.css'

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
)
'''
        (src_dir / "main.tsx").write_text(main_tsx)
        
        entity_name = entities[0].get("name", "Item") if entities else "Item"
        entity_lower = entity_name.lower()
        
        # App.tsx
        app_tsx = f'''import {{ useState, useEffect }} from 'react'

interface {entity_name} {{
  id: string
  title?: string
  name?: string
  createdAt: string
}}

function App() {{
  const [items, setItems] = useState<{entity_name}[]>([])
  const [newItem, setNewItem] = useState('')
  const [loading, setLoading] = useState(true)

  useEffect(() => {{
    fetch('/api/v1/{entity_lower}s')
      .then(res => res.json())
      .then(data => {{
        setItems(Array.isArray(data) ? data : [])
        setLoading(false)
      }})
      .catch(() => setLoading(false))
  }}, [])

  const addItem = async () => {{
    if (!newItem.trim()) return
    const res = await fetch('/api/v1/{entity_lower}s', {{
      method: 'POST',
      headers: {{ 'Content-Type': 'application/json' }},
      body: JSON.stringify({{ title: newItem }})
    }})
    if (res.ok) {{
      const item = await res.json()
      setItems([...items, item])
      setNewItem('')
    }}
  }}

  const deleteItem = async (id: string) => {{
    await fetch(`/api/v1/{entity_lower}s/${{id}}`, {{ method: 'DELETE' }})
    setItems(items.filter(i => i.id !== id))
  }}

  return (
    <div style={{ padding: '2rem', fontFamily: 'system-ui' }}>
      <h1>{app_name}</h1>
      
      <div style={{ marginBottom: '1rem' }}>
        <input
          value={{newItem}}
          onChange={{e => setNewItem(e.target.value)}}
          placeholder="Add new item..."
          style={{ padding: '0.5rem', marginRight: '0.5rem', width: '300px' }}
        />
        <button onClick={{addItem}} style={{ padding: '0.5rem 1rem' }}>Add</button>
      </div>

      {{loading ? (
        <p>Loading...</p>
      ) : (
        <ul style={{ listStyle: 'none', padding: 0 }}>
          {{items.map(item => (
            <li key={{item.id}} style={{
              display: 'flex',
              justifyContent: 'space-between',
              padding: '0.5rem',
              borderBottom: '1px solid #eee',
              maxWidth: '400px'
            }}>
              <span>{{item.title || item.name || item.id}}</span>
              <button onClick={{() => deleteItem(item.id)}} style={{ color: 'red' }}>×</button>
            </li>
          ))}}
        </ul>
      )}}
    </div>
  )
}}

export default App
'''
        (src_dir / "App.tsx").write_text(app_tsx)
        
        # Index.css
        index_css = '''* {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
  background: #f5f5f5;
  color: #333;
}

button {
  cursor: pointer;
  border: none;
  background: #007bff;
  color: white;
  border-radius: 4px;
}

button:hover {
  background: #0056b3;
}

input {
  border: 1px solid #ddd;
  border-radius: 4px;
}
'''
        (src_dir / "index.css").write_text(index_css)
        
        # TypeScript config
        tsconfig = {
            "compilerOptions": {
                "target": "ES2020",
                "useDefineForClassFields": True,
                "lib": ["ES2020", "DOM", "DOM.Iterable"],
                "module": "ESNext",
                "skipLibCheck": True,
                "moduleResolution": "bundler",
                "allowImportingTsExtensions": True,
                "resolveJsonModule": True,
                "isolatedModules": True,
                "noEmit": True,
                "jsx": "react-jsx",
                "strict": True
            },
            "include": ["src"]
        }
        (frontend_dir / "tsconfig.json").write_text(json.dumps(tsconfig, indent=2))
    
    async def generate_dockerfile(self, target_dir: str) -> None:
        """Generate Dockerfile for the API"""
        dockerfile_content = '''FROM node:20-alpine

WORKDIR /app

COPY api/package*.json ./
RUN npm ci --only=production

COPY api/src ./src
COPY api/tsconfig.json ./

RUN npm run build

EXPOSE 3000

CMD ["node", "dist/index.js"]
'''
        dockerfile_path = Path(target_dir) / "Dockerfile"
        dockerfile_path.write_text(dockerfile_content)
        
        compose_content = '''version: '3.8'

services:
  api:
    build: .
    ports:
      - "3000:3000"
    environment:
      - NODE_ENV=production
    restart: unless-stopped
'''
        compose_path = Path(target_dir) / "docker-compose.yml"
        compose_path.write_text(compose_content)
    
    async def generate_cicd(self, target_dir: str) -> None:
        """Generate CI/CD templates"""
        github_dir = Path(target_dir) / ".github" / "workflows"
        github_dir.mkdir(parents=True, exist_ok=True)
        
        ci_content = '''name: CI

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: cd api && npm ci
      - run: cd api && npm run build
      - run: cd api && npm test
'''
        ci_path = github_dir / "ci.yml"
        ci_path.write_text(ci_content)
    
    async def generate_docs(self, target_dir: str) -> None:
        """Generate README documentation"""
        if not self._contract:
            return
        
        app_name = self._contract.get("definition", {}).get("app", {}).get("name", "App")
        entities = self._contract.get("definition", {}).get("entities", [])
        
        entity_docs = ""
        for entity in entities:
            name = entity.get("name", "Entity")
            fields = entity.get("fields", [])
            field_list = "\n".join([f"  - `{f.get('name')}`: {f.get('type')}" for f in fields])
            entity_docs += f"\n### {name}\n{field_list}\n"
        
        readme_content = f'''# {app_name}

Generated by Reclapp Evolution Engine.

## Quick Start

```bash
cd api
npm install
npm run dev
```

## API Endpoints

- `GET /health` - Health check
- `GET /api/v1/{{entity}}s` - List all
- `GET /api/v1/{{entity}}s/:id` - Get by ID
- `POST /api/v1/{{entity}}s` - Create
- `PUT /api/v1/{{entity}}s/:id` - Update
- `DELETE /api/v1/{{entity}}s/:id` - Delete

## Entities
{entity_docs}

## Docker

```bash
docker-compose up -d
```

## Development

Generated with ❤️ by Reclapp
'''
        readme_path = Path(target_dir) / "README.md"
        readme_path.write_text(readme_content)
    
    async def generate_test_fixtures(self, target_dir: str) -> None:
        """Generate test fixture files"""
        if not self._contract:
            return
        
        entities = self._contract.get("definition", {}).get("entities", [])
        fixtures_dir = Path(target_dir) / "tests" / "fixtures"
        fixtures_dir.mkdir(parents=True, exist_ok=True)
        
        for entity in entities:
            name = entity.get("name", "Item")
            name_lower = name.lower()
            
            fixture = {
                "valid": {
                    "title": f"Test {name}",
                    "description": f"A test {name_lower} for E2E testing",
                    "status": "pending"
                },
                "invalid": {
                    "description": "Missing required title field"
                },
                "update": {
                    "title": f"Updated {name}",
                    "description": "Updated description",
                    "status": "completed"
                }
            }
            
            fixture_path = fixtures_dir / f"{name_lower}.fixture.json"
            fixture_path.write_text(json.dumps(fixture, indent=2))
    
    async def generate_test_config(self, target_dir: str) -> None:
        """Generate test configuration file"""
        config = {
            "baseUrl": f"http://localhost:{self._port}",
            "apiPrefix": "/api/v1",
            "timeout": 5000,
            "retries": 3
        }
        
        config_path = Path(target_dir) / "tests" / "test.config.ts"
        config_content = f'''// Test Configuration
// Generated by Reclapp Evolution Engine

export const testConfig = {{
  baseUrl: '{config["baseUrl"]}',
  apiPrefix: '{config["apiPrefix"]}',
  timeout: {config["timeout"]},
  retries: {config["retries"]}
}};

export const getApiUrl = (path: string) => 
  `${{testConfig.baseUrl}}${{testConfig.apiPrefix}}${{path}}`;
'''
        config_path.write_text(config_content)
    
    async def generate_e2e_tests(self, target_dir: str) -> Optional[str]:
        """Generate E2E test file"""
        if not self._contract:
            return None
        
        entities = self._contract.get("definition", {}).get("entities", [])
        if not entities:
            return None
        
        entity = entities[0]
        entity_name = entity.get("name", "Item")
        entity_lower = entity_name.lower()
        
        test_content = f'''/**
 * E2E Tests for {entity_name} API
 * Auto-generated by Evolution Manager
 */

const BASE_URL = 'http://localhost:{self._port}';

interface TestResult {{
  name: string;
  passed: boolean;
  error?: string;
}}

async function runTests(): Promise<TestResult[]> {{
  const results: TestResult[] = [];
  let createdId: string | null = null;

  // Health check
  try {{
    const res = await fetch(`${{BASE_URL}}/health`);
    results.push({{ name: 'Health check', passed: res.ok }});
  }} catch (e) {{
    results.push({{ name: 'Health check', passed: false, error: String(e) }});
  }}

  // CREATE
  try {{
    const res = await fetch(`${{BASE_URL}}/api/v1/{entity_lower}s`, {{
      method: 'POST',
      headers: {{ 'Content-Type': 'application/json' }},
      body: JSON.stringify({{ title: 'Test {entity_name}' }})
    }});
    const data = await res.json();
    createdId = data.id;
    results.push({{ name: 'CREATE POST', passed: res.status === 201 && !!createdId }});
  }} catch (e) {{
    results.push({{ name: 'CREATE POST', passed: false, error: String(e) }});
  }}

  // READ ALL
  try {{
    const res = await fetch(`${{BASE_URL}}/api/v1/{entity_lower}s`);
    results.push({{ name: 'READ ALL', passed: res.ok }});
  }} catch (e) {{
    results.push({{ name: 'READ ALL', passed: false, error: String(e) }});
  }}

  // READ ONE
  if (createdId) {{
    try {{
      const res = await fetch(`${{BASE_URL}}/api/v1/{entity_lower}s/${{createdId}}`);
      results.push({{ name: 'READ ONE', passed: res.ok }});
    }} catch (e) {{
      results.push({{ name: 'READ ONE', passed: false, error: String(e) }});
    }}
  }}

  // UPDATE
  if (createdId) {{
    try {{
      const res = await fetch(`${{BASE_URL}}/api/v1/{entity_lower}s/${{createdId}}`, {{
        method: 'PUT',
        headers: {{ 'Content-Type': 'application/json' }},
        body: JSON.stringify({{ title: 'Updated {entity_name}' }})
      }});
      results.push({{ name: 'UPDATE PUT', passed: res.ok }});
    }} catch (e) {{
      results.push({{ name: 'UPDATE PUT', passed: false, error: String(e) }});
    }}
  }}

  // DELETE
  if (createdId) {{
    try {{
      const res = await fetch(`${{BASE_URL}}/api/v1/{entity_lower}s/${{createdId}}`, {{
        method: 'DELETE'
      }});
      results.push({{ name: 'DELETE', passed: res.status === 204 || res.ok }});
    }} catch (e) {{
      results.push({{ name: 'DELETE', passed: false, error: String(e) }});
    }}
  }}

  return results;
}}

runTests().then(results => {{
  console.log(JSON.stringify(results));
  const failed = results.filter(r => !r.passed).length;
  process.exit(failed > 0 ? 1 : 0);
}});
'''
        
        test_file = Path(target_dir) / "tests" / "e2e" / "api.e2e.ts"
        test_file.parent.mkdir(parents=True, exist_ok=True)
        test_file.write_text(test_content)
        return str(test_file)
