"""
GitHub integration connector.
"""

import logging
import os
from datetime import datetime, timedelta
from typing import Any, Dict, Optional

import requests

from app.integrations.base import BaseConnector

logger = logging.getLogger(__name__)


class GitHubConnector(BaseConnector):
    """GitHub integration connector."""

    display_name = "GitHub"
    description = "Sync issues and track time from GitHub"
    icon = "github"

    @property
    def provider_name(self) -> str:
        return "github"

    def get_authorization_url(self, redirect_uri: str, state: str = None) -> str:
        """Get GitHub OAuth authorization URL."""
        from app.models import Settings

        settings = Settings.get_settings()
        creds = settings.get_integration_credentials("github")
        client_id = creds.get("client_id") or os.getenv("GITHUB_CLIENT_ID")
        if not client_id:
            raise ValueError("GITHUB_CLIENT_ID not configured")

        scopes = ["repo", "issues:read", "issues:write", "user:email"]

        auth_url = "https://github.com/login/oauth/authorize"
        params = {"client_id": client_id, "redirect_uri": redirect_uri, "scope": " ".join(scopes), "state": state or ""}

        query_string = "&".join([f"{k}={v}" for k, v in params.items()])
        return f"{auth_url}?{query_string}"

    def exchange_code_for_tokens(self, code: str, redirect_uri: str) -> Dict[str, Any]:
        """Exchange authorization code for tokens."""
        from app.models import Settings

        settings = Settings.get_settings()
        creds = settings.get_integration_credentials("github")
        client_id = creds.get("client_id") or os.getenv("GITHUB_CLIENT_ID")
        client_secret = creds.get("client_secret") or os.getenv("GITHUB_CLIENT_SECRET")

        if not client_id or not client_secret:
            raise ValueError("GitHub OAuth credentials not configured")

        token_url = "https://github.com/login/oauth/access_token"

        response = requests.post(
            token_url,
            data={"client_id": client_id, "client_secret": client_secret, "code": code, "redirect_uri": redirect_uri},
            headers={"Accept": "application/json"},
        )

        response.raise_for_status()
        data = response.json()

        if "error" in data:
            raise ValueError(f"GitHub OAuth error: {data.get('error_description', data.get('error'))}")

        # GitHub tokens don't expire by default, but can be configured
        expires_at = None
        if "expires_in" in data:
            expires_at = datetime.utcnow() + timedelta(seconds=data["expires_in"])

        # Get user info
        access_token = data.get("access_token")
        user_info = {}
        if access_token:
            try:
                user_response = requests.get(
                    "https://api.github.com/user",
                    headers={"Authorization": f"token {access_token}", "Accept": "application/vnd.github.v3+json"},
                )
                if user_response.status_code == 200:
                    user_info = user_response.json()
            except Exception as e:
                logger.debug("GitHub user fetch failed: %s", e)

        return {
            "access_token": access_token,
            "refresh_token": data.get("refresh_token"),  # GitHub doesn't provide refresh tokens by default
            "expires_at": expires_at,
            "token_type": data.get("token_type", "Bearer"),
            "scope": data.get("scope"),
            "extra_data": {
                "user_login": user_info.get("login"),
                "user_name": user_info.get("name"),
                "user_email": user_info.get("email"),
            },
        }

    def refresh_access_token(self) -> Dict[str, Any]:
        """Refresh access token (GitHub tokens typically don't expire)."""
        # GitHub tokens don't expire by default
        # If using GitHub Apps, refresh would be handled differently
        if not self.credentials or not self.credentials.access_token:
            raise ValueError("No access token available")

        # For now, just return the existing token
        # In production, implement proper refresh if using GitHub Apps
        return {
            "access_token": self.credentials.access_token,
            "refresh_token": self.credentials.refresh_token,
            "expires_at": self.credentials.expires_at,
        }

    def test_connection(self) -> Dict[str, Any]:
        """Test connection to GitHub."""
        token = self.get_access_token()
        if not token:
            return {"success": False, "message": "No access token available"}

        api_url = "https://api.github.com/user"

        try:
            response = requests.get(
                api_url, headers={"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"}
            )

            if response.status_code == 200:
                user_data = response.json()
                return {"success": True, "message": f"Connected as {user_data.get('login', 'Unknown')}"}
            else:
                return {"success": False, "message": f"API returned status {response.status_code}"}
        except Exception as e:
            return {"success": False, "message": f"Connection error: {str(e)}"}

    def sync_data(self, sync_type: str = "full") -> Dict[str, Any]:
        """Sync issues from GitHub repositories and create tasks."""
        import logging
        from datetime import datetime, timedelta

        from app import db
        from app.models import Project, Task
        from app.utils.integration_sync_context import (
            ensure_project_integration_fields,
            find_project_by_integration_ref,
            find_task_by_integration_ref,
            require_sync_context,
            set_task_integration_ref,
        )

        logger = logging.getLogger(__name__)

        token = self.get_access_token()
        if not token:
            return {"success": False, "message": "No access token available. Please reconnect the integration."}

        try:
            actor_id, client_id = require_sync_context(self.integration)
        except ValueError as e:
            return {"success": False, "message": str(e)}

        # Get repositories from config
        repos_str = self.integration.config.get("repositories", "")
        if not repos_str:
            # Get user's repositories
            try:
                repos_response = requests.get(
                    "https://api.github.com/user/repos",
                    headers={"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"},
                    timeout=30,
                )
                if repos_response.status_code == 200:
                    repos = repos_response.json()
                    repos_list = [f"{r['owner']['login']}/{r['name']}" for r in repos[:10]]  # Limit to 10 repos
                elif repos_response.status_code == 401:
                    return {
                        "success": False,
                        "message": "GitHub authentication failed. Please reconnect the integration.",
                    }
                else:
                    error_msg = (
                        f"Could not fetch repositories: {repos_response.status_code} - {repos_response.text[:200]}"
                    )
                    logger.error(error_msg)
                    return {"success": False, "message": error_msg}
            except requests.exceptions.Timeout:
                return {"success": False, "message": "GitHub API request timed out. Please try again."}
            except requests.exceptions.ConnectionError as e:
                return {"success": False, "message": f"Failed to connect to GitHub API: {str(e)}"}
            except Exception as e:
                logger.error(f"Error fetching repositories: {e}", exc_info=True)
                return {"success": False, "message": f"Error fetching repositories: {str(e)}"}
        else:
            repos_list = [r.strip() for r in repos_str.split(",") if r.strip()]

        if not repos_list:
            return {"success": False, "message": "No repositories configured or found"}

        synced_count = 0
        errors = []

        try:
            for repo in repos_list:
                try:
                    if "/" not in repo:
                        errors.append(f"Invalid repository format: {repo} (expected owner/repo)")
                        continue

                    owner, repo_name = repo.split("/", 1)

                    # Find or create project (client + custom_fields integration marker)
                    project = find_project_by_integration_ref(client_id, "github", repo)
                    if not project:
                        project = Project.query.filter_by(client_id=client_id, name=repo).first()
                    if not project:
                        try:
                            project = Project(
                                name=repo,
                                client_id=client_id,
                                description=f"GitHub repository: {repo}",
                                status="active",
                            )
                            db.session.add(project)
                            db.session.flush()
                        except Exception as e:
                            errors.append(f"Error creating project for {repo}: {str(e)}")
                            logger.error(f"Error creating project for {repo}: {e}", exc_info=True)
                            continue
                    ensure_project_integration_fields(
                        project,
                        source="github",
                        ref=repo,
                        display_name=repo,
                        description=f"GitHub repository: {repo}",
                    )

                    # Fetch issues
                    try:
                        issues_response = requests.get(
                            f"https://api.github.com/repos/{repo}/issues",
                            headers={"Authorization": f"token {token}", "Accept": "application/vnd.github.v3+json"},
                            params={"state": "open", "per_page": 100},
                            timeout=30,
                        )

                        if issues_response.status_code == 404:
                            errors.append(f"Repository {repo} not found or access denied")
                            continue
                        elif issues_response.status_code == 401:
                            errors.append(f"Authentication failed for repository {repo}")
                            continue
                        elif issues_response.status_code != 200:
                            error_text = issues_response.text[:200] if issues_response.text else ""
                            errors.append(
                                f"Error fetching issues for {repo}: {issues_response.status_code} - {error_text}"
                            )
                            continue

                        issues = issues_response.json()
                    except requests.exceptions.Timeout:
                        errors.append(f"Timeout fetching issues for {repo}")
                        continue
                    except requests.exceptions.ConnectionError as e:
                        errors.append(f"Connection error for {repo}: {str(e)}")
                        continue
                    except Exception as e:
                        errors.append(f"Error fetching issues for {repo}: {str(e)}")
                        logger.error(f"Error fetching issues for {repo}: {e}", exc_info=True)
                        continue

                    for issue in issues:
                        try:
                            if issue.get("pull_request"):
                                continue
                            issue_number = issue.get("number")
                            issue_title = (issue.get("title") or "").strip() or "Issue"
                            issue_title = issue_title[:180]

                            if not issue_number:
                                continue

                            issue_ref = f"{repo}#{issue_number}"
                            body = (issue.get("body") or "").strip()
                            url = issue.get("html_url") or ""
                            if url:
                                body = f"{body}\n\nGitHub: {url}" if body else f"GitHub: {url}"
                            gh_state = (issue.get("state") or "").lower()
                            task_status = "done" if gh_state == "closed" else "todo"

                            task = find_task_by_integration_ref(project.id, issue_ref, source="github")
                            if not task:
                                try:
                                    task_name = f"#{issue_number}: {issue_title}"[:200]
                                    task = Task(
                                        project_id=project.id,
                                        name=task_name,
                                        description=body or None,
                                        status=task_status,
                                        created_by=actor_id,
                                    )
                                    db.session.add(task)
                                    db.session.flush()
                                except Exception as e:
                                    errors.append(f"Error creating task for issue #{issue_number} in {repo}: {str(e)}")
                                    logger.error(
                                        f"Error creating task for issue #{issue_number} in {repo}: {e}", exc_info=True
                                    )
                                    continue
                            else:
                                task.name = f"#{issue_number}: {issue_title}"[:200]
                                task.description = body or None
                                task.status = task_status

                            set_task_integration_ref(
                                task,
                                source="github",
                                ref=issue_ref,
                                extra={
                                    "issue_number": issue_number,
                                    "issue_id": issue.get("id"),
                                    "url": url,
                                    "repo": repo,
                                },
                            )

                            synced_count += 1
                        except Exception as e:
                            errors.append(f"Error syncing issue #{issue.get('number', 'unknown')} in {repo}: {str(e)}")
                            logger.error(
                                f"Error syncing issue #{issue.get('number', 'unknown')} in {repo}: {e}", exc_info=True
                            )
                except ValueError as e:
                    errors.append(f"Invalid repository format: {repo} - {str(e)}")
                except Exception as e:
                    errors.append(f"Error syncing repository {repo}: {str(e)}")
                    logger.error(f"Error syncing repository {repo}: {e}", exc_info=True)

            try:
                db.session.commit()
            except Exception as e:
                db.session.rollback()
                error_msg = f"Database error during sync: {str(e)}"
                errors.append(error_msg)
                logger.error(error_msg, exc_info=True)
                return {"success": False, "message": error_msg, "synced_items": synced_count, "errors": errors}

            if errors:
                return {
                    "success": True,
                    "message": f"Sync completed with {len(errors)} error(s). Synced {synced_count} issues.",
                    "synced_items": synced_count,
                    "errors": errors,
                }

            return {
                "success": True,
                "message": f"Sync completed. Synced {synced_count} issues.",
                "synced_items": synced_count,
                "errors": errors,
            }
        except Exception as e:
            logger.error(f"GitHub sync failed: {e}", exc_info=True)
            try:
                db.session.rollback()
            except Exception as rollback_err:
                logger.debug("Rollback after GitHub sync failure: %s", rollback_err)
            return {
                "success": False,
                "message": f"Sync failed: {str(e)}",
                "errors": errors,
                "synced_items": synced_count,
            }

    def handle_webhook(
        self, payload: Dict[str, Any], headers: Dict[str, str], raw_body: Optional[bytes] = None
    ) -> Dict[str, Any]:
        """Handle incoming webhook from GitHub."""
        import hashlib
        import hmac
        import logging

        logger = logging.getLogger(__name__)

        try:
            # Verify webhook signature if secret is configured
            signature = headers.get("X-Hub-Signature-256", "")
            if signature:
                # Get webhook secret from integration config
                webhook_secret = self.integration.config.get("webhook_secret") if self.integration else None

                if webhook_secret:
                    # GitHub sends signature as "sha256=<hash>"
                    if not signature.startswith("sha256="):
                        logger.warning("GitHub webhook signature format invalid (expected sha256= prefix)")
                        return {"success": False, "message": "Invalid webhook signature format"}

                    signature_hash = signature[7:]  # Remove "sha256=" prefix

                    # GitHub signs the raw request body bytes, not the parsed JSON
                    # This is critical for signature verification to work correctly
                    if raw_body is None:
                        # Fallback: try to reconstruct from payload (not ideal but better than nothing)
                        import json

                        raw_body = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
                        logger.warning(
                            "GitHub webhook: Using reconstructed payload for signature verification (raw body not available)"
                        )

                    # Compute expected signature using raw body bytes
                    expected_signature = hmac.new(webhook_secret.encode("utf-8"), raw_body, hashlib.sha256).hexdigest()

                    # Use constant-time comparison to prevent timing attacks
                    if not hmac.compare_digest(signature_hash, expected_signature):
                        logger.warning("GitHub webhook signature verification failed")
                        return {"success": False, "message": "Webhook signature verification failed"}

                    logger.debug("GitHub webhook signature verified successfully")
                else:
                    # Signature provided but no secret configured - reject for security
                    logger.warning("GitHub webhook signature provided but no secret configured - rejecting webhook")
                    return {"success": False, "message": "Webhook secret not configured"}
            else:
                # No signature: always reject (configure secret on GitHub + matching webhook_secret here)
                webhook_secret = self.integration.config.get("webhook_secret") if self.integration else None
                if webhook_secret:
                    logger.warning("GitHub webhook secret configured but no signature provided - rejecting webhook")
                    return {"success": False, "message": "Webhook signature required but not provided"}
                logger.warning(
                    "GitHub webhook rejected: missing X-Hub-Signature-256. "
                    "Set a secret on the GitHub webhook and store it in integration config as webhook_secret."
                )
                return {
                    "success": False,
                    "message": "Webhook signature required; configure webhook_secret on GitHub and in TimeTracker.",
                }

            # Process webhook event
            action = payload.get("action")
            event_type = headers.get("X-GitHub-Event", "")

            if event_type == "issues":
                issue = payload.get("issue", {})
                issue_number = issue.get("number")
                repo = payload.get("repository", {}).get("full_name", "")

                return {
                    "success": True,
                    "message": f"Webhook received for issue #{issue_number} in {repo}",
                    "event_type": f"{event_type}.{action}",
                }
            elif event_type == "pull_request":
                pr = payload.get("pull_request", {})
                pr_number = pr.get("number")
                repo = payload.get("repository", {}).get("full_name", "")

                return {
                    "success": True,
                    "message": f"Webhook received for PR #{pr_number} in {repo}",
                    "event_type": f"{event_type}.{action}",
                }

            return {"success": True, "message": f"Webhook processed: {event_type}"}
        except ValueError as e:
            # Handle validation errors
            logger.error(f"GitHub webhook validation error: {e}")
            return {"success": False, "message": f"Webhook validation error: {str(e)}"}
        except Exception as e:
            # Handle all other errors
            logger.error(f"GitHub webhook processing error: {e}", exc_info=True)
            return {"success": False, "message": f"Error processing webhook: {str(e)}"}

    def get_config_schema(self) -> Dict[str, Any]:
        """Get configuration schema."""
        return {
            "fields": [
                {
                    "name": "repositories",
                    "label": "Repositories",
                    "type": "text",
                    "required": False,
                    "placeholder": "owner/repo1, owner/repo2",
                    "help": "Comma-separated list of repositories to sync (e.g., 'octocat/Hello-World, owner/repo'). Leave empty to sync all accessible repositories.",
                    "description": "Which GitHub repositories to sync",
                },
                {
                    "name": "sync_direction",
                    "type": "select",
                    "label": "Sync Direction",
                    "options": [
                        {"value": "github_to_timetracker", "label": "GitHub → TimeTracker (Import only)"},
                        {"value": "timetracker_to_github", "label": "TimeTracker → GitHub (Export only)"},
                        {"value": "bidirectional", "label": "Bidirectional (Two-way sync)"},
                    ],
                    "default": "github_to_timetracker",
                    "description": "Choose how data flows between GitHub and TimeTracker",
                },
                {
                    "name": "sync_items",
                    "type": "array",
                    "label": "Items to Sync",
                    "options": [
                        {"value": "issues", "label": "Issues"},
                        {"value": "pull_requests", "label": "Pull Requests"},
                        {"value": "projects", "label": "Projects (Repositories)"},
                    ],
                    "default": ["issues"],
                    "description": "Select which items to synchronize",
                },
                {
                    "name": "issue_states",
                    "type": "array",
                    "label": "Issue States to Sync",
                    "options": [
                        {"value": "open", "label": "Open Issues"},
                        {"value": "closed", "label": "Closed Issues"},
                        {"value": "all", "label": "All Issues"},
                    ],
                    "default": ["open"],
                    "description": "Which issue states to include in sync",
                },
                {
                    "name": "auto_sync",
                    "type": "boolean",
                    "label": "Auto Sync",
                    "default": False,
                    "description": "Automatically sync when webhooks are received from GitHub",
                },
                {
                    "name": "sync_interval",
                    "type": "select",
                    "label": "Sync Schedule",
                    "options": [
                        {"value": "manual", "label": "Manual only"},
                        {"value": "hourly", "label": "Every hour"},
                        {"value": "daily", "label": "Daily"},
                        {"value": "weekly", "label": "Weekly"},
                    ],
                    "default": "manual",
                    "description": "How often to automatically sync data",
                },
                {
                    "name": "create_projects",
                    "type": "boolean",
                    "label": "Create Projects",
                    "default": True,
                    "description": "Automatically create projects in TimeTracker from GitHub repositories",
                },
                {
                    "name": "webhook_secret",
                    "label": "Webhook Secret",
                    "type": "password",
                    "required": False,
                    "placeholder": "Enter webhook secret from GitHub",
                    "help": "Secret token for verifying webhook signatures. Configure this in your GitHub repository webhook settings.",
                    "description": "Security token for webhook verification",
                },
            ],
            "required": [],
            "sections": [
                {
                    "title": "Repository Settings",
                    "description": "Configure which repositories to sync",
                    "fields": ["repositories", "create_projects"],
                },
                {
                    "title": "Sync Settings",
                    "description": "Configure what and how to sync",
                    "fields": ["sync_direction", "sync_items", "issue_states", "auto_sync", "sync_interval"],
                },
                {
                    "title": "Webhook Settings",
                    "description": "Configure webhook security",
                    "fields": ["webhook_secret"],
                },
            ],
            "sync_settings": {
                "enabled": True,
                "auto_sync": False,
                "sync_interval": "manual",
                "sync_direction": "github_to_timetracker",
                "sync_items": ["issues"],
            },
        }
