#!/usr/bin/env python3
"""
Devour Lighthouse-Style Scorecard Generator.
Creates circular gauge charts and comprehensive metrics visualization.
"""

from __future__ import annotations

import json
import logging
import math
import os
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Tuple, Any, Optional

try:
    from PIL import Image, ImageDraw, ImageFont
except ImportError:
    print("Error: PIL/Pillow required. Install with: pip install Pillow")
    sys.exit(1)

logger = logging.getLogger(__name__)

# Visual constants
SCALE = 2
BG = (248, 248, 246)
FRAME = (222, 222, 220)
BORDER = (200, 200, 198)
ACCENT = (88, 166, 255)
TEXT = (40, 44, 52)
DIM = (140, 140, 140)
BG_SCORE = (255, 255, 255)
BG_TABLE = (255, 255, 255)
BG_ROW_ALT = (250, 250, 248)

# Color palette for gauges
COLORS = {
    'excellent': (68, 120, 68),      # deep sage
    'good': (120, 140, 72),         # olive green  
    'moderate': (145, 155, 80),      # yellow-green
    'poor': (255, 193, 7),          # orange
    'critical': (220, 38, 127),        # red
}

@dataclass
class LighthouseData:
    """Data structure for Lighthouse-style visualization."""
    project_name: str
    version: str
    overall_score: float
    overall_grade: str
    categories: Dict[str, Dict[str, Any]]
    metrics: Dict[str, Any]
    timestamp: str


def load_font(size: int, *, bold: bool = False) -> ImageFont.ImageFont:
    """Load font with fallback."""
    size = size * SCALE
    candidates = [
        "/System/Library/Fonts/SFCompact.ttf",
        "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
        "arial.ttf"
    ]
    
    if bold:
        candidates = [
            "/System/Library/Fonts/SFCompact.ttf",
            "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
            "arialbd.ttf"
        ]
    
    for path in candidates:
        try:
            if os.path.exists(path):
                return ImageFont.truetype(path, size)
        except OSError:
            continue
    
    return ImageFont.load_default()


def scale(value: int) -> int:
    """Scale value by retina factor."""
    return value * SCALE


def score_to_color(score: float) -> Tuple[int, int, int]:
    """Convert score to color."""
    if score >= 90:
        return COLORS['excellent']
    elif score >= 70:
        return COLORS['good']
    elif score >= 50:
        return COLORS['moderate']
    elif score >= 30:
        return COLORS['poor']
    else:
        return COLORS['critical']


def draw_circular_gauge(
    draw: ImageDraw.ImageDraw,
    cx: int, cy: int, radius: int,
    score: float, max_score: float = 100,
    label: str = "SCORE"
) -> None:
    """Draw a circular gauge like Lighthouse."""
    # Background circle
    draw.ellipse([cx - radius, cy - radius, cx + radius, cy + radius], 
                 fill=BG_TABLE, outline=BORDER, width=2)
    
    # Calculate angle for score (270° to -90° range)
    start_angle = 270
    score_angle = start_angle - (score / max_score) * 360
    
    # Draw colored arc
    if score > 0:
        draw.pieslice([cx - radius, cy - radius, cx + radius, cy + radius],
                     start=start_angle, end=score_angle, 
                     fill=score_to_color(score))
    
    # Inner circle
    inner_radius = radius * 0.7
    draw.ellipse([cx - inner_radius, cy - inner_radius, cx + inner_radius, cy + inner_radius],
                 fill=BG_SCORE, outline=BORDER, width=1)
    
    # Score text
    font_score = load_font(24, bold=True)
    score_text = f"{int(score)}"
    bbox = draw.textbbox((0, 0), score_text, font=font_score)
    text_width = bbox[2] - bbox[0]
    text_height = bbox[3] - bbox[1]
    
    draw.text((cx - text_width // 2, cy - text_height // 2 + bbox[1]), 
               score_text, fill=TEXT, font=font_score)
    
    # Label text
    font_label = load_font(10)
    label_bbox = draw.textbbox((0, 0), label, font=font_label)
    label_width = label_bbox[2] - label_bbox[0]
    
    draw.text((cx - label_width // 2, cy + radius * 0.4), 
               label, fill=DIM, font=font_label)


def draw_metric_bar(
    draw: ImageDraw.ImageDraw,
    x: int, y: int, width: int, height: int,
    value: float, max_value: float,
    label: str, color: Tuple[int, int, int]
) -> None:
    """Draw a horizontal metric bar."""
    # Background
    draw.rectangle([x, y, x + width, y + height], fill=BG_TABLE, outline=BORDER)
    
    # Fill bar
    if max_value > 0:
        fill_width = int((value / max_value) * width)
        draw.rectangle([x, y, x + fill_width, y + height], fill=color)
    
    # Label
    font = load_font(9)
    text = f"{label}: {int(value)}/{int(max_value)}"
    draw.text((x + 5, y + 2), text, fill=TEXT, font=font)


def draw_category_section(
    draw: ImageDraw.ImageDraw,
    x: int, y: int, width: int, height: int,
    title: str, score: float, issues: List[Dict[str, Any]]
) -> None:
    """Draw a category section with gauge and issues."""
    # Title
    font_title = load_font(12, bold=True)
    draw.text((x, y), title, fill=TEXT, font=font_title)
    
    # Gauge
    gauge_y = y + 25
    gauge_size = 60
    draw_circular_gauge(draw, x + gauge_size // 2, gauge_y + gauge_size // 2, 
                     gauge_size // 2, score, label="")
    
    # Issues list
    issues_y = gauge_y + gauge_size + 20
    font_issue = load_font(8)
    
    for i, issue in enumerate(issues[:5]):  # Limit to 5 issues
        issue_text = f"• {issue.get('title', 'Unknown')}"
        if len(issue_text) > 35:
            issue_text = issue_text[:32] + "..."
        
        draw.text((x, issues_y + i * 12), issue_text, fill=DIM, font=font_issue)


def generate_lighthouse_scorecard(data: LighthouseData, output_path: str | Path) -> Path:
    """Generate Lighthouse-style scorecard with circular gauges."""
    output_path = Path(output_path)
    
    # Layout
    width = scale(800)
    height = scale(600)
    margin = scale(20)
    
    # Create image
    img = Image.new("RGB", (width, height), BG)
    draw = ImageDraw.Draw(img)
    
    # Header
    font_header = load_font(20, bold=True)
    title = f"{data.project_name} - Code Health Report"
    title_bbox = draw.textbbox((0, 0), title, font=font_header)
    title_width = title_bbox[2] - title_bbox[0]
    
    draw.text((margin, margin), title, fill=TEXT, font=font_header)
    
    # Overall score gauge (large, centered)
    overall_x = width // 2
    overall_y = margin + 50
    overall_radius = scale(80)
    
    draw_circular_gauge(draw, overall_x, overall_y, overall_radius,
                       score=data.overall_score, label="OVERALL")
    
    # Grade text
    font_grade = load_font(32, bold=True)
    grade_text = data.overall_grade
    grade_bbox = draw.textbbox((0, 0), grade_text, font=font_grade)
    grade_width = grade_bbox[2] - grade_bbox[0]
    grade_height = grade_bbox[3] - grade_bbox[1]
    
    grade_y = overall_y + overall_radius + scale(30)
    draw.text((overall_x - grade_width // 2, grade_y - grade_bbox[1]), 
               grade_text, fill=score_to_color(data.overall_score), font=font_grade)
    
    # Category gauges (2x2 grid)
    categories_start_y = grade_y + grade_height + scale(40)
    category_width = scale(180)
    category_height = scale(150)
    category_spacing = scale(20)
    
    col_x = margin
    row_y = categories_start_y
    
    for i, (category_name, category_data) in enumerate(data.categories.items()):
        if i > 0 and i % 2 == 0:
            col_x += category_width + category_spacing
            row_y = categories_start_y
        
        if i % 2 == 1:
            row_y += category_height + category_spacing
        
        draw_category_section(
            draw, col_x, row_y, category_width, category_height,
            title=category_name.replace('_', ' ').title(),
            score=category_data.get('score', 0),
            issues=category_data.get('issues', [])
        )
    
    # Metrics summary
    metrics_y = row_y + category_height + scale(40)
    font_metrics = load_font(10)
    
    metrics_text = [
        f"Generated: {data.timestamp}",
        f"Total Issues: {data.metrics.get('total_issues', 0)}",
        f"Critical: {data.metrics.get('critical_issues', 0)}",
        f"Resolution Rate: {data.metrics.get('resolution_rate', 0):.1f}%"
    ]
    
    for i, text in enumerate(metrics_text):
        draw.text((margin, metrics_y + i * 15), text, fill=DIM, font=font_metrics)
    
    # Save
    output_path.parent.mkdir(parents=True, exist_ok=True)
    img.save(str(output_path), "PNG", optimize=True)
    return output_path


def load_font(size: int, *, bold: bool = False) -> ImageFont.ImageFont:
    """Load font with fallback."""
    size = size * SCALE
    candidates = [
        "/System/Library/Fonts/SFCompact.ttf",
        "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
        "arial.ttf"
    ]
    
    if bold:
        candidates = [
            "/System/Library/Fonts/SFCompact.ttf",
            "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf",
            "arialbd.ttf"
        ]
    
    for path in candidates:
        try:
            if os.path.exists(path):
                return ImageFont.truetype(path, size)
        except OSError:
            continue
    
    return ImageFont.load_default()


def load_devour_lighthouse_data(json_path: str) -> LighthouseData:
    """Load Devour data and convert to Lighthouse format."""
    with open(json_path, 'r') as f:
        data = json.load(f)
    
    findings = data.get('findings', [])
    
    # Calculate overall score
    total_score = sum(f.get('score', 0) * int(f.get('severity', 1)) for f in findings)
    overall_score = max(0, 100 - (total_score / 1000 * 100))
    
    # Grade
    if overall_score >= 90:
        grade = "A"
    elif overall_score >= 80:
        grade = "B"
    elif overall_score >= 70:
        grade = "C"
    elif overall_score >= 60:
        grade = "D"
    else:
        grade = "F"
    
    # Group by category
    categories = {}
    type_counts = {}
    type_scores = {}
    
    for finding in findings:
        ftype = finding.get('type', 'unknown')
        type_counts[ftype] = type_counts.get(ftype, 0) + 1
        type_scores[ftype] = type_scores.get(ftype, 0) + finding.get('score', 0)
    
    # Create categories with scores and sample issues
    for ftype, count in type_counts.items():
        avg_score = 100 - (type_scores[ftype] / max(1, count) / 10 * 100)
        category_score = max(0, min(100, avg_score))
        
        # Get sample issues for this category
        category_issues = [
            {
                'title': f.get('title', 'Unknown'),
                'score': f.get('score', 0)
            }
            for f in findings 
            if f.get('type') == ftype
        ][:3]  # Top 3 issues
        
        categories[ftype.replace('_', ' ').title()] = {
            'score': category_score,
            'issues': category_issues
        }
    
    # Metrics
    metrics = {
        'total_issues': len(findings),
        'critical_issues': len([f for f in findings if f.get('severity') == 4]),
        'resolution_rate': 0.0  # Would calculate from fixed/resolved
    }
    
    return LighthouseData(
        project_name="Devour",
        version="1.0.0",
        overall_score=overall_score,
        overall_grade=grade,
        categories=categories,
        metrics=metrics,
        timestamp=data.get('timestamp', '')
    )


def main():
    """Main entry point."""
    if len(sys.argv) != 3:
        print("Usage: python devour_lighthouse.py <devour_results.json> <output.png>")
        sys.exit(1)
    
    json_path = sys.argv[1]
    output_path = sys.argv[2]
    
    if not os.path.exists(json_path):
        print(f"Error: Input file {json_path} not found")
        sys.exit(1)
    
    try:
        data = load_devour_lighthouse_data(json_path)
        result_path = generate_lighthouse_scorecard(data, output_path)
        print(f"Lighthouse scorecard generated: {result_path}")
        
    except Exception as e:
        print(f"Error generating Lighthouse scorecard: {e}")
        sys.exit(1)


if __name__ == "__main__":
    main()
