"""
Local file backend plugin for issue management.

This plugin provides offline issue management using markdown files with YAML frontmatter.
"""

import os
import re
import yaml
import subprocess
from datetime import datetime
from pathlib import Path
from typing import List, Optional, Dict, Any

from ..base import IssueBackend
from domain.issues.models import Issue, IssueState, Label


class LocalPlugin(IssueBackend):
    """Local file-based backend plugin."""

    def __init__(self, config: Dict[str, Any]):
        """Initialize local plugin with configuration."""
        super().__init__(config)

        self.issues_dir = Path(config.get('directory', '.markitect/issues'))
        self.auto_git = config.get('auto_git', True)

        self._setup_directory_structure()
        self._load_local_config()

    def _setup_directory_structure(self):
        """Create necessary directory structure."""
        self.issues_dir.mkdir(parents=True, exist_ok=True)
        (self.issues_dir / 'open').mkdir(exist_ok=True)
        (self.issues_dir / 'closed').mkdir(exist_ok=True)

    def _load_local_config(self):
        """Load or create local configuration."""
        config_file = self.issues_dir / 'config.yml'

        if config_file.exists():
            with open(config_file, 'r') as f:
                self.local_config = yaml.safe_load(f) or {}
        else:
            self.local_config = {'next_issue_number': self.config.get('numbering_start', 1)}
            self._save_local_config()

    def _save_local_config(self):
        """Save local configuration."""
        config_file = self.issues_dir / 'config.yml'
        with open(config_file, 'w') as f:
            yaml.dump(self.local_config, f, default_flow_style=False)

    def list_issues(self, state: Optional[str] = None) -> List[Issue]:
        """List issues from local files."""
        issues = []

        if state == 'open' or state is None or state == 'all':
            issues.extend(self._read_issues_from_directory(self.issues_dir / 'open'))

        if state == 'closed' or state is None or state == 'all':
            issues.extend(self._read_issues_from_directory(self.issues_dir / 'closed'))

        # Sort by issue number
        issues.sort(key=lambda x: x.number)
        return issues

    def _read_issues_from_directory(self, directory: Path) -> List[Issue]:
        """Read all issues from a directory."""
        issues = []

        if not directory.exists():
            return issues

        for file_path in directory.glob('*.md'):
            try:
                issue = self._read_issue_file(file_path)
                issues.append(issue)
            except Exception:
                # Skip malformed files
                continue

        return issues

    def _read_issue_file(self, file_path: Path) -> Issue:
        """Read issue from markdown file with YAML frontmatter."""
        with open(file_path, 'r', encoding='utf-8') as f:
            content = f.read()

        # Split frontmatter and body
        if content.startswith('---\n'):
            try:
                parts = content.split('---\n', 2)
                if len(parts) >= 3:
                    frontmatter = yaml.safe_load(parts[1])
                    body = parts[2].strip()
                else:
                    frontmatter = {}
                    body = content
            except yaml.YAMLError:
                raise yaml.YAMLError(f"Invalid YAML in {file_path}")
        else:
            frontmatter = {}
            body = content

        # Convert string labels to Label objects
        label_objects = []
        for label in frontmatter.get('labels', []):
            if isinstance(label, str):
                label_objects.append(Label(name=label))
            else:
                label_objects.append(label)

        # Map state string to IssueState enum
        state_str = frontmatter.get('state', 'open')
        issue_state = IssueState.OPEN if state_str == 'open' else IssueState.CLOSED

        # Create Issue object
        issue = Issue(
            number=frontmatter.get('number', 0),
            title=frontmatter.get('title', ''),
            state=issue_state,
            labels=label_objects,
            created_at=datetime.fromisoformat(frontmatter.get('created_at', datetime.now().isoformat())),
            updated_at=datetime.fromisoformat(frontmatter.get('updated_at', datetime.now().isoformat())),
            assignee=frontmatter.get('assignee'),
            milestone=frontmatter.get('milestone')
        )

        # Store body separately since domain model doesn't have it
        issue._body = body

        return issue

    def get_issue(self, issue_id: str) -> Issue:
        """Get specific issue by ID."""
        file_path = self._find_issue_file(issue_id)
        if not file_path:
            raise FileNotFoundError(f"Issue {issue_id} not found")

        return self._read_issue_file(file_path)

    def _find_issue_file(self, issue_id: str) -> Optional[Path]:
        """Find issue file in open or closed directories."""
        # Convert issue_id to 3-digit format to match filename pattern
        issue_num = f"{int(issue_id):03d}"
        pattern = f"{issue_num}-*.md"

        # Search in open directory
        for file_path in (self.issues_dir / 'open').glob(pattern):
            return file_path

        # Search in closed directory
        for file_path in (self.issues_dir / 'closed').glob(pattern):
            return file_path

        return None

    def create_issue(self, title: str, body: str, **kwargs) -> Issue:
        """Create new issue as local file."""
        issue_number = self.local_config.get('next_issue_number', 1)

        # Convert string labels to Label objects
        label_objects = []
        for label in kwargs.get('labels', []):
            if isinstance(label, str):
                label_objects.append(Label(name=label))
            else:
                label_objects.append(label)

        # Create Issue object
        issue = Issue(
            number=issue_number,
            title=title,
            state=IssueState.OPEN,
            labels=label_objects,
            created_at=datetime.now(),
            updated_at=datetime.now(),
            assignee=kwargs.get('assignee'),
            milestone=kwargs.get('milestone')
        )

        # Store body separately since domain model doesn't have it
        issue._body = body  # Temporary storage for body content

        # Write to file
        self._write_issue_file(issue, self.issues_dir / 'open')

        # Update counter
        self.local_config['next_issue_number'] = issue_number + 1
        self._save_local_config()

        # Git integration
        if self.auto_git:
            self._git_add_and_commit(f"Create issue #{issue_number}: {title}")

        return issue

    def _write_issue_file(self, issue: Issue, directory: Path):
        """Write issue to markdown file with YAML frontmatter."""
        filename = self._generate_filename(issue)
        file_path = directory / filename

        # Convert Label objects to strings for YAML
        label_names = [label.name for label in issue.labels]

        # Prepare frontmatter
        frontmatter = {
            'number': issue.number,
            'title': issue.title,
            'state': issue.state.value,
            'created_at': issue.created_at.isoformat(),
            'updated_at': issue.updated_at.isoformat(),
            'labels': label_names,
            'assignee': issue.assignee,
            'milestone': issue.milestone
        }

        # Write file
        with open(file_path, 'w', encoding='utf-8') as f:
            f.write('---\n')
            yaml.dump(frontmatter, f, default_flow_style=False)
            f.write('---\n\n')
            f.write(getattr(issue, '_body', ''))

    def _generate_filename(self, issue: Issue) -> str:
        """Generate safe filename from issue."""
        # Sanitize title for filename
        safe_title = re.sub(r'[^\w\s-]', '', issue.title.lower())
        safe_title = re.sub(r'[\s_-]+', '-', safe_title)
        safe_title = safe_title.strip('-')[:50]  # Limit length

        return f"{issue.number:03d}-{safe_title}.md"

    def add_comment(self, issue_id: str, comment: str) -> Dict[str, Any]:
        """Add comment to local issue file."""
        if not comment.strip():
            raise ValueError("Comment cannot be empty")
        if not issue_id.strip():
            raise ValueError("Issue ID cannot be empty")

        # For now, return mock comment data
        # Full implementation would append to issue file
        return {
            'id': f"comment_{datetime.now().timestamp()}",
            'body': comment,
            'timestamp': datetime.now().isoformat()
        }

    def close_issue(self, issue_id: str) -> Issue:
        """Close issue by moving to closed directory."""
        return self.update_issue(issue_id, state=IssueState.CLOSED)

    def update_issue(self, issue_id: str, **kwargs) -> Issue:
        """Update issue properties."""
        file_path = self._find_issue_file(issue_id)
        if not file_path:
            raise FileNotFoundError(f"Issue {issue_id} not found")

        # Read current issue
        issue = self._read_issue_file(file_path)

        # Update properties
        for key, value in kwargs.items():
            if hasattr(issue, key):
                setattr(issue, key, value)

        # Handle state change (move file if needed)
        old_state = 'open' if 'open' in str(file_path) else 'closed'
        new_state_obj = kwargs.get('state', issue.state)
        new_state = new_state_obj.value if hasattr(new_state_obj, 'value') else str(new_state_obj)

        if new_state != old_state:
            # Remove old file
            file_path.unlink()

            # Write to new directory
            new_directory = self.issues_dir / new_state
            self._write_issue_file(issue, new_directory)
        else:
            # Update existing file
            self._write_issue_file(issue, file_path.parent)

        # Git integration
        if self.auto_git:
            self._git_add_and_commit(f"Update issue #{issue.number}")

        return issue

    def _git_add_and_commit(self, message: str):
        """Add and commit changes to git."""
        try:
            subprocess.run(['git', 'add', str(self.issues_dir)],
                         cwd='.', check=True, capture_output=True)
            subprocess.run(['git', 'commit', '-m', message],
                         cwd='.', check=True, capture_output=True)
        except (subprocess.CalledProcessError, FileNotFoundError):
            # Git not available or not a git repo, ignore
            pass