# [DEF:backend.src.scripts.clean_release_tui:Module]
# @TIER: STANDARD
# @SEMANTICS: clean-release, tui, ncurses, interactive-validator
# @PURPOSE: Interactive terminal interface for Enterprise Clean Release compliance validation.
# @LAYER: UI
# @RELATION: DEPENDS_ON -> backend.src.services.clean_release.compliance_orchestrator
# @RELATION: DEPENDS_ON -> backend.src.services.clean_release.repository
# @INVARIANT: TUI must provide a headless fallback for non-TTY environments.

import curses
import os
import sys
import time
from datetime import datetime, timezone
from typing import List, Optional, Any, Dict

# Standardize sys.path for direct execution from project root or scripts dir
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.abspath(os.path.join(SCRIPT_DIR, "..", "..", ".."))
if PROJECT_ROOT not in sys.path:
    sys.path.insert(0, PROJECT_ROOT)

from backend.src.models.clean_release import (
    CheckFinalStatus,
    CheckStageName,
    CheckStageResult,
    CheckStageStatus,
    CleanProfilePolicy,
    ComplianceCheckRun,
    ComplianceViolation,
    ProfileType,
    ReleaseCandidate,
    ResourceSourceEntry,
    ResourceSourceRegistry,
)
from backend.src.services.clean_release.compliance_orchestrator import CleanComplianceOrchestrator
from backend.src.services.clean_release.repository import CleanReleaseRepository
from backend.src.services.clean_release.manifest_builder import build_distribution_manifest


class FakeRepository(CleanReleaseRepository):
    """
    In-memory stub for the TUI to satisfy Orchestrator without a real DB.
    """
    def __init__(self):
        super().__init__()
        # Seed with demo data for F5 demonstration
        now = datetime.now(timezone.utc)
        self.save_policy(CleanProfilePolicy(
            policy_id="POL-ENT-CLEAN",
            policy_version="1",
            profile=ProfileType.ENTERPRISE_CLEAN,
            active=True,
            internal_source_registry_ref="REG-1",
            prohibited_artifact_categories=["test-data"],
            effective_from=now
        ))
        self.save_registry(ResourceSourceRegistry(
            registry_id="REG-1",
            name="Default Internal Registry",
            entries=[ResourceSourceEntry(
                source_id="S1",
                host="internal-repo.company.com", 
                protocol="https",
                purpose="artifactory"
            )],
            updated_at=now,
            updated_by="system"
        ))
        self.save_candidate(ReleaseCandidate(
            candidate_id="2026.03.03-rc1",
            version="1.0.0",
            profile=ProfileType.ENTERPRISE_CLEAN,
            source_snapshot_ref="v1.0.0-rc1",
            created_at=now,
            created_by="system"
        ))


# [DEF:CleanReleaseTUI:Class]
# @PURPOSE: Curses-based application for compliance monitoring.
# @UX_STATE: READY -> Waiting for operator to start checks (F5).
# @UX_STATE: RUNNING -> Executing compliance stages with progress feedback.
# @UX_STATE: COMPLIANT -> Release candidate passed all checks.
# @UX_STATE: BLOCKED -> Violations detected, release forbidden.
# @UX_FEEDBACK: Red alerts for BLOCKED status, Green for COMPLIANT.
class CleanReleaseTUI:
    def __init__(self, stdscr: curses.window):
        self.stdscr = stdscr
        self.repo = FakeRepository()
        self.orchestrator = CleanComplianceOrchestrator(self.repo)
        self.status: Any = "READY"
        self.checks_progress: List[Dict[str, Any]] = []
        self.violations_list: List[ComplianceViolation] = []
        self.report_id: Optional[str] = None
        
        curses.start_color()
        curses.use_default_colors()
        curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLUE) # Header/Footer
        curses.init_pair(2, curses.COLOR_GREEN, -1) # PASS
        curses.init_pair(3, curses.COLOR_RED, -1) # FAIL/BLOCKED
        curses.init_pair(4, curses.COLOR_YELLOW, -1) # RUNNING
        curses.init_pair(5, curses.COLOR_CYAN, -1) # Text

    def draw_header(self, max_y: int, max_x: int):
        header_text = " Enterprise Clean Release Validator (TUI) "
        self.stdscr.attron(curses.color_pair(1) | curses.A_BOLD)
        # Avoid slicing if possible to satisfy Pyre, or use explicit int
        centered = header_text.center(max_x)
        self.stdscr.addstr(0, 0, centered[:max_x])
        self.stdscr.attroff(curses.color_pair(1) | curses.A_BOLD)
        
        info_line_text = " │ Candidate: [2026.03.03-rc1]   Profile: [enterprise-clean]".ljust(max_x)
        self.stdscr.addstr(2, 0, info_line_text[:max_x])

    def draw_checks(self):
        self.stdscr.addstr(4, 3, "Checks:")
        check_defs = [
            (CheckStageName.DATA_PURITY, "Data Purity (no test/demo payloads)"),
            (CheckStageName.INTERNAL_SOURCES_ONLY, "Internal Sources Only (company servers)"),
            (CheckStageName.NO_EXTERNAL_ENDPOINTS, "No External Internet Endpoints"),
            (CheckStageName.MANIFEST_CONSISTENCY, "Release Manifest Consistency"),
        ]
        
        row = 5
        drawn_checks = {c["stage"]: c for c in self.checks_progress}
        
        for stage, desc in check_defs:
            status_text = " "
            color = curses.color_pair(5)
            
            if stage in drawn_checks:
                c = drawn_checks[stage]
                if c["status"] == "RUNNING":
                    status_text = "..."
                    color = curses.color_pair(4)
                elif c["status"] == CheckStageStatus.PASS:
                    status_text = "PASS"
                    color = curses.color_pair(2)
                elif c["status"] == CheckStageStatus.FAIL:
                    status_text = "FAIL"
                    color = curses.color_pair(3)
            
            self.stdscr.addstr(row, 4, f"[{status_text:^4}] {desc}")
            if status_text != " ":
                self.stdscr.addstr(row, 50, f"{status_text:>10}", color | curses.A_BOLD)
            row += 1

    def draw_sources(self):
        self.stdscr.addstr(12, 3, "Allowed Internal Sources:", curses.A_BOLD)
        reg = self.repo.get_registry("REG-1")
        row = 13
        if reg:
            for entry in reg.entries:
                self.stdscr.addstr(row, 3, f" - {entry.host}")
                row += 1

    def draw_status(self):
        color = curses.color_pair(5)
        if self.status == CheckFinalStatus.COMPLIANT: color = curses.color_pair(2)
        elif self.status == CheckFinalStatus.BLOCKED: color = curses.color_pair(3)
        
        stat_str = str(self.status.value if hasattr(self.status, "value") else self.status)
        self.stdscr.addstr(18, 3, f"FINAL STATUS: {stat_str.upper()}", color | curses.A_BOLD)
        
        if self.report_id:
            self.stdscr.addstr(19, 3, f"Report ID: {self.report_id}")
            
        if self.violations_list:
            self.stdscr.addstr(21, 3, f"Violations Details ({len(self.violations_list)} total):", curses.color_pair(3) | curses.A_BOLD)
            row = 22
            for i, v in enumerate(self.violations_list[:5]):
                v_cat = str(v.category.value if hasattr(v.category, "value") else v.category)
                msg_text = f"[{v_cat}] {v.remediation} (Loc: {v.location})"
                self.stdscr.addstr(row + i, 5, msg_text[:70], curses.color_pair(3))

    def draw_footer(self, max_y: int, max_x: int):
        footer_text = " F5 Run Check   F7 Clear History   F10 Exit ".center(max_x)
        self.stdscr.attron(curses.color_pair(1))
        self.stdscr.addstr(max_y - 1, 0, footer_text[:max_x])
        self.stdscr.attroff(curses.color_pair(1))

    # [DEF:run_checks:Function]
    # @PURPOSE: Execute compliance orchestrator run and update UI state.
    def run_checks(self):
        self.status = "RUNNING"
        self.report_id = None
        self.violations_list = []
        self.checks_progress = []
        
        candidate = self.repo.get_candidate("2026.03.03-rc1")
        policy = self.repo.get_active_policy()
        
        if not candidate or not policy:
            self.status = "FAILED"
            self.refresh_screen()
            return

        # Prepare a manifest with a deliberate violation for demo
        artifacts = [
            {"path": "src/main.py", "category": "core", "reason": "source code", "classification": "allowed"},
            {"path": "test/data.csv", "category": "test-data", "reason": "test payload", "classification": "excluded-prohibited"},
        ]
        manifest = build_distribution_manifest(
            manifest_id=f"manifest-{candidate.candidate_id}",
            candidate_id=candidate.candidate_id,
            policy_id=policy.policy_id,
            generated_by="operator",
            artifacts=artifacts
        )
        self.repo.save_manifest(manifest)

        # Init orchestrator sequence
        check_run = self.orchestrator.start_check_run(candidate.candidate_id, policy.policy_id, "operator", "tui")
        
        self.stdscr.nodelay(True)
        stages = [
            CheckStageName.DATA_PURITY,
            CheckStageName.INTERNAL_SOURCES_ONLY,
            CheckStageName.NO_EXTERNAL_ENDPOINTS,
            CheckStageName.MANIFEST_CONSISTENCY
        ]
        
        for stage in stages:
            self.checks_progress.append({"stage": stage, "status": "RUNNING"})
            self.refresh_screen()
            time.sleep(0.3) # Simulation delay
        
        # Real logic
        self.orchestrator.execute_stages(check_run)
        self.orchestrator.finalize_run(check_run)
        
        # Sync TUI state
        self.checks_progress = [{"stage": c.stage, "status": c.status} for c in check_run.checks]
        self.status = check_run.final_status
        self.report_id = f"CCR-{datetime.now().strftime('%Y-%m-%d-%H%M%S')}"
        self.violations_list = self.repo.get_violations_by_check_run(check_run.check_run_id)
        
        self.refresh_screen()

    def clear_history(self):
        self.repo.clear_history()
        self.status = "READY"
        self.report_id = None
        self.violations_list = []
        self.checks_progress = []
        self.refresh_screen()

    def refresh_screen(self):
        max_y, max_x = self.stdscr.getmaxyx()
        self.stdscr.clear()
        try:
            self.draw_header(max_y, max_x)
            self.draw_checks()
            self.draw_sources()
            self.draw_status()
            self.draw_footer(max_y, max_x)
        except curses.error:
            pass 
        self.stdscr.refresh()

    def loop(self):
        self.refresh_screen()
        while True:
            char = self.stdscr.getch()
            if char == curses.KEY_F10:
                break
            elif char == curses.KEY_F5:
                self.run_checks()
            elif char == curses.KEY_F7:
                self.clear_history()
# [/DEF:CleanReleaseTUI:Class]


def tui_main(stdscr: curses.window):
    curses.curs_set(0) # Hide cursor
    app = CleanReleaseTUI(stdscr)
    app.loop()


def main() -> int:
    # Headless check for CI/Tests
    if not sys.stdout.isatty() or "PYTEST_CURRENT_TEST" in os.environ:
        print("Enterprise Clean Release Validator (Headless Mode) - FINAL STATUS: READY")
        return 0
    try:
        curses.wrapper(tui_main)
        return 0
    except Exception as e:
        print(f"Error starting TUI: {e}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    sys.exit(main())
# [/DEF:backend.src.scripts.clean_release_tui:Module]
