from __future__ import annotations
_P='fingerprint'
_O='py_bytes'
_N='status'
_M='UPDATE releases SET is_current = 0 WHERE slug = ?'
_L='created_at'
_K='slug'
_J='source_owner'
_I='source_mtime'
_H='actor'
_G='auto_deploy'
_F='last_py_bytes'
_E='last_fingerprint'
_D='source_path'
_C='current_release_id'
_B=True
_A=None
import sqlite3
from contextlib import contextmanager
from dataclasses import dataclass
from datetime import datetime,timezone
from pathlib import Path
from typing import Iterator,Optional
from admin import config
def _utc_now():return datetime.now(timezone.utc).strftime('%Y-%m-%d %H:%M:%S UTC')
@dataclass
class Deployment:
	id:int;name:str;slug:str;source_type:str;source_path:Optional[str];git_url:Optional[str];git_branch:Optional[str];entrypoint:str;requirements_path:str;app_path:str;status:str;last_error:Optional[str];created_at:str;updated_at:str;last_fingerprint:Optional[str]=_A;last_py_bytes:int=0;auto_deploy:bool=_B;current_release_id:Optional[int]=_A
	@property
	def public_url(self):return f"{config.PUBLIC_BASE_URL}{config.APPS_URL_PREFIX}/{self.slug}/"
@dataclass
class DeployEvent:id:int;slug:str;event_type:str;status:str;message:Optional[str];release_id:Optional[int];py_bytes:Optional[int];fingerprint:Optional[str];created_at:str;actor:Optional[str]=_A;source_mtime:Optional[str]=_A;source_owner:Optional[str]=_A
@dataclass
class Release:id:int;slug:str;label:str;fingerprint:Optional[str];py_bytes:int;archive_path:str;is_current:bool;created_at:str
class Database:
	def __init__(A,db_path=_A):A.db_path=Path(db_path or config.DATA_ROOT/'ivistaar.db')
	def init(B):
		B.db_path.parent.mkdir(parents=_B,exist_ok=_B)
		with B.connect()as A:A.executescript("\n                CREATE TABLE IF NOT EXISTS deployments (\n                    id INTEGER PRIMARY KEY AUTOINCREMENT,\n                    name TEXT NOT NULL,\n                    slug TEXT NOT NULL UNIQUE,\n                    source_type TEXT NOT NULL,\n                    source_path TEXT,\n                    git_url TEXT,\n                    git_branch TEXT,\n                    entrypoint TEXT NOT NULL DEFAULT 'app:app',\n                    requirements_path TEXT NOT NULL DEFAULT 'requirements.txt',\n                    app_path TEXT NOT NULL,\n                    status TEXT NOT NULL DEFAULT 'pending',\n                    last_error TEXT,\n                    created_at TEXT NOT NULL,\n                    updated_at TEXT NOT NULL,\n                    last_fingerprint TEXT,\n                    last_py_bytes INTEGER DEFAULT 0,\n                    auto_deploy INTEGER DEFAULT 1,\n                    current_release_id INTEGER\n                );\n\n                CREATE TABLE IF NOT EXISTS deploy_events (\n                    id INTEGER PRIMARY KEY AUTOINCREMENT,\n                    slug TEXT NOT NULL,\n                    event_type TEXT NOT NULL,\n                    status TEXT NOT NULL,\n                    message TEXT,\n                    release_id INTEGER,\n                    py_bytes INTEGER,\n                    fingerprint TEXT,\n                    created_at TEXT NOT NULL,\n                    actor TEXT,\n                    source_mtime TEXT,\n                    source_owner TEXT\n                );\n\n                CREATE TABLE IF NOT EXISTS releases (\n                    id INTEGER PRIMARY KEY AUTOINCREMENT,\n                    slug TEXT NOT NULL,\n                    label TEXT NOT NULL,\n                    fingerprint TEXT,\n                    py_bytes INTEGER DEFAULT 0,\n                    archive_path TEXT NOT NULL,\n                    is_current INTEGER DEFAULT 0,\n                    created_at TEXT NOT NULL\n                );\n\n                CREATE TABLE IF NOT EXISTS license_state (\n                    id INTEGER PRIMARY KEY CHECK (id = 1),\n                    tier TEXT NOT NULL DEFAULT 'free',\n                    spc_code TEXT,\n                    expires_on TEXT,\n                    activated_by TEXT,\n                    activated_at TEXT,\n                    updated_at TEXT\n                );\n\n                CREATE INDEX IF NOT EXISTS idx_events_slug ON deploy_events(slug, created_at DESC);\n                CREATE INDEX IF NOT EXISTS idx_releases_slug ON releases(slug, created_at DESC);\n                ");B._migrate(A);A.execute("INSERT OR IGNORE INTO license_state (id, tier, updated_at) VALUES (1, 'free', ?)",(_utc_now(),));A.commit()
	def _migrate(H,conn):
		A=conn;D={A[1]for A in A.execute('PRAGMA table_info(deployments)')};E={_D:'ALTER TABLE deployments ADD COLUMN source_path TEXT',_E:'ALTER TABLE deployments ADD COLUMN last_fingerprint TEXT',_F:'ALTER TABLE deployments ADD COLUMN last_py_bytes INTEGER DEFAULT 0',_G:'ALTER TABLE deployments ADD COLUMN auto_deploy INTEGER DEFAULT 1',_C:'ALTER TABLE deployments ADD COLUMN current_release_id INTEGER'}
		for(B,C)in E.items():
			if B not in D:A.execute(C)
		F={A[1]for A in A.execute('PRAGMA table_info(deploy_events)')};G={_H:'ALTER TABLE deploy_events ADD COLUMN actor TEXT',_I:'ALTER TABLE deploy_events ADD COLUMN source_mtime TEXT',_J:'ALTER TABLE deploy_events ADD COLUMN source_owner TEXT'}
		for(B,C)in G.items():
			if B not in F:A.execute(C)
	@contextmanager
	def connect(self):
		A=sqlite3.connect(self.db_path);A.row_factory=sqlite3.Row
		try:yield A
		finally:A.close()
	def list_deployments(A):
		with A.connect()as B:C=B.execute('SELECT * FROM deployments ORDER BY updated_at DESC').fetchall()
		return[A._row_to_deployment(B)for B in C]
	def get_by_slug(A,slug):
		with A.connect()as C:B=C.execute('SELECT * FROM deployments WHERE slug = ?',(slug,)).fetchone()
		return A._row_to_deployment(B)if B else _A
	def get_by_id(A,deployment_id):
		with A.connect()as C:B=C.execute('SELECT * FROM deployments WHERE id = ?',(deployment_id,)).fetchone()
		return A._row_to_deployment(B)if B else _A
	def create(A,*,name,slug,source_type,app_path,entrypoint='app:app',requirements_path='requirements.txt',source_path=_A,git_url=_A,git_branch=_A,status='pending',auto_deploy=_B):
		B=_utc_now()
		with A.connect()as C:D=C.execute('\n                INSERT INTO deployments (\n                    name, slug, source_type, source_path, git_url, git_branch,\n                    entrypoint, requirements_path, app_path, status,\n                    last_error, created_at, updated_at, auto_deploy\n                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, ?, ?, ?)\n                ',(name,slug,source_type,source_path,git_url,git_branch,entrypoint,requirements_path,app_path,status,B,B,1 if auto_deploy else 0));C.commit();E=int(D.lastrowid)
		return A.get_by_id(E)
	def update_status(B,slug,status,last_error=_A):
		with B.connect()as A:A.execute('\n                UPDATE deployments\n                SET status = ?, last_error = ?, updated_at = ?\n                WHERE slug = ?\n                ',(status,last_error,_utc_now(),slug));A.commit()
	def update_fingerprint(E,slug,fingerprint,py_bytes,current_release_id=_A):
		D=current_release_id;C=py_bytes;B=fingerprint
		with E.connect()as A:
			if D is _A:A.execute('\n                    UPDATE deployments\n                    SET last_fingerprint = ?, last_py_bytes = ?, updated_at = ?\n                    WHERE slug = ?\n                    ',(B,C,_utc_now(),slug))
			else:A.execute('\n                    UPDATE deployments\n                    SET last_fingerprint = ?, last_py_bytes = ?,\n                        current_release_id = ?, updated_at = ?\n                    WHERE slug = ?\n                    ',(B,C,D,_utc_now(),slug))
			A.commit()
	def set_auto_deploy(B,slug,enabled):
		with B.connect()as A:A.execute('\n                UPDATE deployments SET auto_deploy = ?, updated_at = ? WHERE slug = ?\n                ',(1 if enabled else 0,_utc_now(),slug));A.commit()
	def delete(C,slug):
		B=slug
		with C.connect()as A:A.execute('DELETE FROM deployments WHERE slug = ?',(B,));A.execute('DELETE FROM deploy_events WHERE slug = ?',(B,));A.execute('DELETE FROM releases WHERE slug = ?',(B,));A.commit()
	def add_event(A,*,slug,event_type,status,message=_A,release_id=_A,py_bytes=_A,fingerprint=_A,actor=_A,source_mtime=_A,source_owner=_A):
		C=_utc_now()
		with A.connect()as B:D=B.execute('\n                INSERT INTO deploy_events (\n                    slug, event_type, status, message, release_id,\n                    py_bytes, fingerprint, created_at,\n                    actor, source_mtime, source_owner\n                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n                ',(slug,event_type,status,message,release_id,py_bytes,fingerprint,C,actor,source_mtime,source_owner));B.commit();E=int(D.lastrowid)
		return A.get_event(E)
	def get_event(A,event_id):
		with A.connect()as C:B=C.execute('SELECT * FROM deploy_events WHERE id = ?',(event_id,)).fetchone()
		return A._row_to_event(B)if B else _A
	def list_events(A,slug=_A,limit=100):
		B=limit
		with A.connect()as C:
			if slug:D=C.execute('\n                    SELECT * FROM deploy_events\n                    WHERE slug = ?\n                    ORDER BY id DESC LIMIT ?\n                    ',(slug,B)).fetchall()
			else:D=C.execute('\n                    SELECT * FROM deploy_events\n                    ORDER BY id DESC LIMIT ?\n                    ',(B,)).fetchall()
		return[A._row_to_event(B)for B in D]
	def create_release(B,*,slug,label,archive_path,fingerprint,py_bytes,make_current=_B):
		C=make_current;D=_utc_now()
		with B.connect()as A:
			if C:A.execute(_M,(slug,))
			E=A.execute('\n                INSERT INTO releases (\n                    slug, label, fingerprint, py_bytes, archive_path,\n                    is_current, created_at\n                ) VALUES (?, ?, ?, ?, ?, ?, ?)\n                ',(slug,label,fingerprint,py_bytes,archive_path,1 if C else 0,D));A.commit();F=int(E.lastrowid)
		return B.get_release(F)
	def get_release(A,release_id):
		with A.connect()as C:B=C.execute('SELECT * FROM releases WHERE id = ?',(release_id,)).fetchone()
		return A._row_to_release(B)if B else _A
	def list_releases(A,slug,limit=50):
		with A.connect()as B:C=B.execute('\n                SELECT * FROM releases\n                WHERE slug = ?\n                ORDER BY id DESC LIMIT ?\n                ',(slug,limit)).fetchall()
		return[A._row_to_release(B)for B in C]
	def set_current_release(D,slug,release_id):
		C=release_id;B=slug
		with D.connect()as A:A.execute(_M,(B,));A.execute('UPDATE releases SET is_current = 1 WHERE id = ? AND slug = ?',(C,B));A.execute('\n                UPDATE deployments\n                SET current_release_id = ?, updated_at = ?\n                WHERE slug = ?\n                ',(C,_utc_now(),B));A.commit()
	def list_old_releases(A,slug,keep):B=A.list_releases(slug,limit=1000);return B[keep:]
	def delete_release_row(B,release_id):
		with B.connect()as A:A.execute('DELETE FROM releases WHERE id = ?',(release_id,));A.commit()
	def get_license(B):
		with B.connect()as C:
			try:A=C.execute('SELECT * FROM license_state WHERE id = 1').fetchone()
			except sqlite3.OperationalError:return
		if not A:return
		return{B:A[B]for B in A.keys()}
	def set_license(C,*,tier,spc_code,expires_on,activated_by):
		A=_utc_now()
		with C.connect()as B:B.execute('\n                INSERT INTO license_state (\n                    id, tier, spc_code, expires_on, activated_by, activated_at, updated_at\n                ) VALUES (1, ?, ?, ?, ?, ?, ?)\n                ON CONFLICT(id) DO UPDATE SET\n                    tier = excluded.tier,\n                    spc_code = excluded.spc_code,\n                    expires_on = excluded.expires_on,\n                    activated_by = excluded.activated_by,\n                    activated_at = excluded.activated_at,\n                    updated_at = excluded.updated_at\n                ',(tier,spc_code,expires_on,activated_by,A,A));B.commit()
	def clear_license(B):
		C=_utc_now()
		with B.connect()as A:A.execute("\n                INSERT INTO license_state (id, tier, spc_code, expires_on, activated_by, activated_at, updated_at)\n                VALUES (1, 'free', NULL, NULL, NULL, NULL, ?)\n                ON CONFLICT(id) DO UPDATE SET\n                    tier = 'free',\n                    spc_code = NULL,\n                    expires_on = NULL,\n                    activated_by = NULL,\n                    activated_at = NULL,\n                    updated_at = excluded.updated_at\n                ",(C,));A.commit()
	@staticmethod
	def _row_to_deployment(row):A=row;B=A.keys();return Deployment(id=A['id'],name=A['name'],slug=A[_K],source_type=A['source_type'],source_path=A[_D]if _D in B else _A,git_url=A['git_url'],git_branch=A['git_branch'],entrypoint=A['entrypoint'],requirements_path=A['requirements_path'],app_path=A['app_path'],status=A[_N],last_error=A['last_error'],created_at=A[_L],updated_at=A['updated_at'],last_fingerprint=A[_E]if _E in B else _A,last_py_bytes=int(A[_F]or 0)if _F in B else 0,auto_deploy=bool(A[_G])if _G in B else _B,current_release_id=int(A[_C])if _C in B and A[_C]is not _A else _A)
	@staticmethod
	def _row_to_event(row):A=row;B=A.keys();return DeployEvent(id=A['id'],slug=A[_K],event_type=A['event_type'],status=A[_N],message=A['message'],release_id=A['release_id'],py_bytes=A[_O],fingerprint=A[_P],created_at=A[_L],actor=A[_H]if _H in B else _A,source_mtime=A[_I]if _I in B else _A,source_owner=A[_J]if _J in B else _A)
	@staticmethod
	def _row_to_release(row):A=row;return Release(id=A['id'],slug=A[_K],label=A['label'],fingerprint=A[_P],py_bytes=int(A[_O]or 0),archive_path=A['archive_path'],is_current=bool(A['is_current']),created_at=A[_L])
db=Database()
