import argparse
import json
from datetime import datetime, timezone
from pathlib import Path


OUTSTANDING_STATUSES = {"proposed", "in_review", "needs_info", "accepted"}
DENIED_STATUSES = {"rejected"}
APPROVED_STATUSES = {"accepted"}
IMPLEMENTED_STATUSES = {"implemented"}
SUPERSEDED_STATUSES = {"superseded"}
IN_REVIEW_STATUSES = {"in_review"}
NEEDS_INFO_STATUSES = {"needs_info"}
SEVERITIES = ("critical", "high", "medium", "low")
LOCAL_STATE_MARKERS = (".prd_plugin/local", ".prd_plugin\\local")


def _empty_totals():
    return {
        "total": 0,
        "outstanding": 0,
        "denied": 0,
        "approved": 0,
        "implemented": 0,
        "superseded": 0,
        "in_review": 0,
        "needs_info": 0,
        "thread_messages": 0,
        "unresolved_threads": 0,
        "privacy_warnings": 0,
        "stale_requests": 0,
    }


def _request_status(request):
    return str(request.get("status", "proposed")).strip().lower() or "proposed"


def _request_type(request):
    return str(request.get("request_type", "unspecified")).strip().lower() or "unspecified"


def _request_severity(request):
    severity = str(request.get("severity", "unspecified")).strip().lower() or "unspecified"
    if severity in SEVERITIES:
        return severity
    return "unspecified"


def _has_graduation_links(request):
    graduated_to = request.get("graduated_to", [])
    return isinstance(graduated_to, list) and bool(graduated_to)


def _thread_messages(request):
    thread = request.get("thread", {})
    if not isinstance(thread, dict):
        return []
    messages = thread.get("messages", [])
    if not isinstance(messages, list):
        return []
    return messages


def _thread_status(request):
    thread = request.get("thread", {})
    if not isinstance(thread, dict):
        return ""
    return str(thread.get("status", "")).strip().lower()


def _parse_time(value):
    if not value:
        return None
    try:
        parsed = datetime.fromisoformat(str(value).replace("Z", "+00:00"))
    except ValueError:
        return None
    if parsed.tzinfo is None:
        parsed = parsed.replace(tzinfo=timezone.utc)
    return parsed.astimezone(timezone.utc)


def _last_activity_at(request):
    candidates = [
        _parse_time(request.get("updated_at")),
        _parse_time(request.get("reviewed_at")),
        _parse_time(request.get("created_at")),
    ]
    for message in _thread_messages(request):
        if isinstance(message, dict):
            candidates.append(_parse_time(message.get("created_at")))
    candidates = [candidate for candidate in candidates if candidate is not None]
    if not candidates:
        return None
    return max(candidates)


def _is_older_than(timestamp, now, days):
    if timestamp is None or days is None:
        return False
    return (now - timestamp).total_seconds() > int(days) * 24 * 60 * 60


def _contains_local_state_reference(value):
    return any(marker in str(value) for marker in LOCAL_STATE_MARKERS)


def _privacy_warnings_for_request(request, repo_scope, origin_repo):
    warnings = []
    scope = str(request.get("scope", "local")).strip().lower() or "local"
    visibility = str(request.get("visibility", "repo")).strip().lower() or "repo"
    request_id = request.get("id", "<missing-id>")

    allowed_scopes = {"local", "upstream_submission"}
    if repo_scope == "upstream":
        allowed_scopes.add("upstream")

    if scope not in allowed_scopes:
        warnings.append(
            {
                "id": request_id,
                "issue": "request scope is not allowed for this repository",
            }
        )

    if visibility in {"cross_repo", "public"}:
        warnings.append(
            {
                "id": request_id,
                "issue": "cross-repo visibility is disabled by default",
            }
        )

    if (
        origin_repo
        and scope == "local"
        and request.get("origin_repo")
        and request.get("origin_repo") != origin_repo
    ):
        warnings.append(
            {
                "id": request_id,
                "issue": "local request origin does not match this repository",
            }
        )

    for message in _thread_messages(request):
        if _contains_local_state_reference(message):
            warnings.append(
                {
                    "id": request_id,
                    "message_id": message.get("id", "<missing-message-id>")
                    if isinstance(message, dict)
                    else "<invalid-message>",
                    "issue": "thread message references local session state",
                }
            )

    return warnings


def _load_config(config_path):
    path = Path(config_path)
    if not path.exists():
        return {}
    return json.loads(path.read_text(encoding="utf-8"))


def _resolve_relative_to_repo(path_value, config_path):
    path = Path(path_value)
    if path.is_absolute():
        return path

    config = Path(config_path)
    if config.name == "config.json" and config.parent.name == ".prd_plugin":
        return config.parent.parent / path
    return Path.cwd() / path


def resolve_requests_path(explicit_path, default_path, config_path=".prd_plugin/config.json"):
    if explicit_path:
        return Path(explicit_path)

    config = _load_config(config_path)
    configured_path = config.get("paths", {}).get("requests_file")
    if configured_path:
        return _resolve_relative_to_repo(configured_path, config_path)

    return Path(default_path)


def resolve_output_options(explicit_format, explicit_output, config_path=".prd_plugin/config.json"):
    config = _load_config(config_path)
    reports = config.get("reports", {})

    output_format = explicit_format or reports.get("request_report_format") or "markdown"
    if output_format not in {"json", "markdown"}:
        raise ValueError("request report format must be 'json' or 'markdown'")

    output_path = explicit_output or reports.get("request_report_output")
    if output_path:
        output_path = _resolve_relative_to_repo(output_path, config_path)

    return output_format, output_path


def resolve_privacy_options(config_path=".prd_plugin/config.json"):
    config = _load_config(config_path)
    privacy = config.get("privacy", {})
    return {
        "repo_scope": privacy.get("repo_scope", "local"),
        "origin_repo": privacy.get("upstream_hub"),
    }


def resolve_staleness_options(config_path=".prd_plugin/config.json"):
    config = _load_config(config_path)
    requests = config.get("requests", {})
    health = config.get("health", {})
    return {
        "stale_after_days": int(requests.get("stale_after_days", 14)),
        "accepted_request_stale_after_days": int(health.get("accepted_request_stale_after_days", 7)),
    }


def _looks_like_single_request(data):
    return (
        isinstance(data, dict)
        and isinstance(data.get("id"), str)
        and any(key in data for key in ("request_type", "status", "thread", "summary"))
    )


def _requests_from_data(data, path):
    if isinstance(data, dict) and isinstance(data.get("requests"), list):
        return data["requests"]
    if isinstance(data, dict) and "requests" in data:
        raise ValueError(f"{path} must contain a 'requests' array")
    if _looks_like_single_request(data):
        return [data]
    raise ValueError(f"{path} must contain a 'requests' array or a single request object with an 'id'")


def build_report(
    requests_path,
    repo_scope="local",
    origin_repo=None,
    stale_after_days=14,
    accepted_request_stale_after_days=7,
    now=None,
):
    path = Path(requests_path)
    if now is None:
        now = datetime.now(timezone.utc)
    elif now.tzinfo is None:
        now = now.replace(tzinfo=timezone.utc)
    else:
        now = now.astimezone(timezone.utc)

    report = {
        "status": "ok",
        "requests_path": str(path),
        "totals": _empty_totals(),
        "outstanding_requests": [],
        "denied_requests": [],
        "approved_requests": [],
        "implemented_requests": [],
        "implemented_into_plugin": [],
        "threaded_requests": [],
        "stale_requests": [],
        "needs_attention": [],
        "privacy_warnings": [],
        "by_type": {},
        "by_severity": {
            "critical": 0,
            "high": 0,
            "medium": 0,
            "low": 0,
            "unspecified": 0,
        },
    }

    if not path.exists():
        report["status"] = "no_requests_file"
        return report

    data = json.loads(path.read_text(encoding="utf-8"))
    requests = _requests_from_data(data, path)

    report["totals"]["total"] = len(requests)

    for request in requests:
        request_id = request.get("id", "<missing-id>")
        status = _request_status(request)
        request_type = _request_type(request)
        severity = _request_severity(request)

        report["by_type"][request_type] = report["by_type"].get(request_type, 0) + 1
        report["by_severity"][severity] += 1

        if status in OUTSTANDING_STATUSES:
            report["totals"]["outstanding"] += 1
            report["outstanding_requests"].append(request_id)
        if status in DENIED_STATUSES:
            report["totals"]["denied"] += 1
            report["denied_requests"].append(request_id)
        if status in APPROVED_STATUSES:
            report["totals"]["approved"] += 1
            report["approved_requests"].append(request_id)
        if status in IMPLEMENTED_STATUSES:
            report["totals"]["implemented"] += 1
            report["implemented_requests"].append(request_id)
            if _has_graduation_links(request):
                report["implemented_into_plugin"].append(request_id)
            else:
                report["needs_attention"].append(
                    {
                        "id": request_id,
                        "issue": "implemented request has no graduation links",
                    }
                )
        if status in SUPERSEDED_STATUSES:
            report["totals"]["superseded"] += 1
        if status in IN_REVIEW_STATUSES:
            report["totals"]["in_review"] += 1
        if status in NEEDS_INFO_STATUSES:
            report["totals"]["needs_info"] += 1

        messages = _thread_messages(request)
        if messages:
            report["threaded_requests"].append(request_id)
            report["totals"]["thread_messages"] += len(messages)
        if messages and _thread_status(request) not in {"resolved", "closed"}:
            report["totals"]["unresolved_threads"] += 1

        last_activity_at = _last_activity_at(request)
        last_activity_text = last_activity_at.isoformat() if last_activity_at else None
        if status in OUTSTANDING_STATUSES - APPROVED_STATUSES and _is_older_than(
            last_activity_at,
            now,
            stale_after_days,
        ):
            report["totals"]["stale_requests"] += 1
            report["stale_requests"].append(request_id)
            report["needs_attention"].append(
                {
                    "id": request_id,
                    "issue": "outstanding request has no recent activity",
                    "last_activity_at": last_activity_text,
                    "stale_after_days": stale_after_days,
                }
            )

        if (
            status in APPROVED_STATUSES
            and not _has_graduation_links(request)
            and _is_older_than(last_activity_at, now, accepted_request_stale_after_days)
        ):
            report["totals"]["stale_requests"] += 1
            report["stale_requests"].append(request_id)
            report["needs_attention"].append(
                {
                    "id": request_id,
                    "issue": "accepted request has no graduation links after threshold",
                }
            )
            report["needs_attention"].append(
                {
                    "id": request_id,
                    "issue": "accepted request has no recent graduation activity",
                    "last_activity_at": last_activity_text,
                    "stale_after_days": accepted_request_stale_after_days,
                }
            )

        privacy_warnings = _privacy_warnings_for_request(request, repo_scope, origin_repo)
        report["privacy_warnings"].extend(privacy_warnings)
        report["totals"]["privacy_warnings"] += len(privacy_warnings)

    return report


def format_markdown(report):
    totals = report["totals"]
    lines = [
        "# Request Intake Report",
        "",
        f"Status: `{report['status']}`",
        f"Requests file: `{report['requests_path']}`",
        "",
        "| Metric | Count |",
        "| --- | ---: |",
        f"| Total requests | {totals['total']} |",
        f"| Outstanding requests | {totals['outstanding']} |",
        f"| Denied requests | {totals['denied']} |",
        f"| Approved requests | {totals['approved']} |",
        f"| Implemented requests | {totals['implemented']} |",
        f"| Superseded requests | {totals['superseded']} |",
        f"| In-review requests | {totals['in_review']} |",
        f"| Needs-info requests | {totals['needs_info']} |",
        f"| Thread messages | {totals['thread_messages']} |",
        f"| Unresolved threads | {totals['unresolved_threads']} |",
        f"| Stale requests | {totals['stale_requests']} |",
        f"| Privacy warnings | {totals['privacy_warnings']} |",
        "",
        "## Implemented Into Plugin",
        "",
    ]

    implemented = report["implemented_into_plugin"]
    lines.extend(f"- `{request_id}`" for request_id in implemented)
    if not implemented:
        lines.append("- None")

    lines.extend(["", "## Needs Attention", ""])
    attention = report["needs_attention"]
    lines.extend(f"- `{item['id']}`: {item['issue']}" for item in attention)
    if not attention:
        lines.append("- None")

    lines.extend(["", "## Stale Requests", ""])
    stale = report["stale_requests"]
    lines.extend(f"- `{request_id}`" for request_id in stale)
    if not stale:
        lines.append("- None")

    lines.extend(["", "## Privacy Warnings", ""])
    privacy_warnings = report["privacy_warnings"]
    lines.extend(
        f"- `{item['id']}`: {item['issue']}" for item in privacy_warnings
    )
    if not privacy_warnings:
        lines.append("- None")

    lines.extend(["", "## By Type", ""])
    if report["by_type"]:
        lines.extend(
            f"- `{request_type}`: {count}"
            for request_type, count in sorted(report["by_type"].items())
        )
    else:
        lines.append("- None")

    lines.extend(["", "## By Severity", ""])
    for severity in ("critical", "high", "medium", "low", "unspecified"):
        lines.append(f"- `{severity}`: {report['by_severity'][severity]}")

    return "\n".join(lines) + "\n"


def main():
    parser = argparse.ArgumentParser(description="Report PRD Plugin request intake status.")
    parser.add_argument(
        "--requests",
        help="Path to requests.json. Overrides config.",
    )
    parser.add_argument(
        "--config",
        default=".prd_plugin/config.json",
        help="Path to PRD Plugin config.json.",
    )
    parser.add_argument(
        "--format",
        choices=("json", "markdown"),
        help="Output format. Overrides config.",
    )
    parser.add_argument("--output", help="Optional output file. Overrides config.")
    args = parser.parse_args()

    requests_path = resolve_requests_path(
        args.requests,
        ".prd_plugin/state/requests.json",
        args.config,
    )
    privacy_options = resolve_privacy_options(args.config)
    staleness_options = resolve_staleness_options(args.config)
    report = build_report(requests_path, **privacy_options, **staleness_options)
    output_format, output_path = resolve_output_options(args.format, args.output, args.config)

    if output_format == "json":
        content = json.dumps(report, indent=2) + "\n"
    else:
        content = format_markdown(report)

    if output_path:
        Path(output_path).parent.mkdir(parents=True, exist_ok=True)
        Path(output_path).write_text(content, encoding="utf-8")
    else:
        print(content, end="")


if __name__ == "__main__":
    main()
