package db

import (
	"context"
	"database/sql"
	"fmt"
	"time"

	_ "github.com/jackc/pgx/v5/stdlib"
	"tangled.org/core/log"
)

type DBTX interface {
	ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error)
	QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error)
	QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row
}

func Make(ctx context.Context, dbUrl string, maxConns int) (*sql.DB, error) {
	db, err := sql.Open("pgx", dbUrl)
	if err != nil {
		return nil, fmt.Errorf("opening db: %w", err)
	}

	db.SetMaxOpenConns(maxConns)
	db.SetMaxIdleConns(maxConns)
	db.SetConnMaxIdleTime(time.Hour)

	pingCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
	defer cancel()
	if err := db.PingContext(pingCtx); err != nil {
		db.Close()
		return nil, fmt.Errorf("ping db: %w", err)
	}

	conn, err := db.Conn(ctx)
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	_, err = conn.ExecContext(ctx, `
		create table if not exists repos (
			did text not null,
			rkey text not null,
			at_uri text generated always as ('at://' || did || '/' || 'sh.tangled.repo' || '/' || rkey) stored,
			cid text,

			-- record content
			name text not null,
			knot_domain text not null,

			-- sync info
			git_rev text not null,
			repo_sha text not null,
			state text not null default 'pending',
			error_msg text,
			retry_count integer not null default 0,
			retry_after integer not null default 0,
			db_created_at timestamptz not null default now(),
			db_updated_at timestamptz not null default now(),

			constraint repos_pkey primary key (did, rkey)
		);

		-- knot hosts
		create table if not exists hosts (
			hostname text not null,
			no_ssl boolean not null default false,
			status text not null default 'active',
			last_seq bigint not null default -1,
			db_created_at timestamptz not null default now(),
			db_updated_at timestamptz not null default now(),

			constraint hosts_pkey primary key (hostname)
		);

		-- repo language stats at HEAD
		create table if not exists repo_head_languages (
			repo     text    not null, -- repo identifier (did)
			commit   text    not null, -- commit id (oid)
			language text    not null,
			size     integer not null check (size >= 0),

			constraint repo_head_languages_pkey
				primary key (repo, commit, language)
		);

		create index if not exists idx_repos_aturi on repos (at_uri);
		create index if not exists idx_repos_db_updated_at on repos (db_updated_at desc);
		create index if not exists idx_hosts_db_updated_at on hosts (db_updated_at desc);

		create index if not exists idx_repo_head_languages_repo_commit
			on repo_head_languages (repo, commit);

		create or replace function set_updated_at()
		returns trigger as $$
		begin
			new.db_updated_at = now();
			return new;
		end;
		$$ language plpgsql;

		drop trigger if exists repos_set_updated_at on repos;
		create trigger repos_set_updated_at
		before update on repos
		for each row
		execute function set_updated_at();

		drop trigger if exists hosts_set_updated_at on hosts;
		create trigger hosts_set_updated_at
		before update on hosts
		for each row
		execute function set_updated_at();
	`)
	if err != nil {
		return nil, fmt.Errorf("initializing db schema: %w", err)
	}

	if err := RunMigrations(ctx, conn, log.FromContext(ctx), Migrations); err != nil {
		return nil, fmt.Errorf("running migrations: %w", err)
	}

	return db, nil
}
