package postgres

import (
	"context"
	"errors"
	"fmt"

	"github.com/jackc/pgx/v5"
)

type Tenant struct {
	ID         string
	TenantCode string
	Name       string
	Status     string
}

type AdminEventRecord struct {
	ID                         string
	PublicID                   string
	TenantID                   *string
	TenantCode                 *string
	TenantName                 *string
	Slug                       string
	DisplayName                string
	Summary                    *string
	Status                     string
	CurrentReleaseID           *string
	CurrentReleasePubID        *string
	ConfigLabel                *string
	ManifestURL                *string
	ManifestChecksum           *string
	RouteCode                  *string
	PresentationID             *string
	PresentationName           *string
	PresentationType           *string
	ContentBundleID            *string
	ContentBundleName          *string
	ContentEntryURL            *string
	ContentAssetRootURL        *string
	CurrentPresentationID      *string
	CurrentPresentationName    *string
	CurrentPresentationType    *string
	CurrentContentBundleID     *string
	CurrentContentBundleName   *string
	CurrentContentEntryURL     *string
	CurrentContentAssetRootURL *string
	CurrentRuntimeBindingID    *string
	CurrentPlaceID             *string
	CurrentMapAssetID          *string
	CurrentTileReleaseID       *string
	CurrentCourseSetID         *string
	CurrentCourseVariantID     *string
	CurrentCourseVariantName   *string
	CurrentRuntimeRouteCode    *string
}

type CreateAdminEventParams struct {
	PublicID    string
	TenantID    *string
	Slug        string
	DisplayName string
	Summary     *string
	Status      string
}

type UpdateAdminEventParams struct {
	EventID     string
	TenantID    *string
	Slug        string
	DisplayName string
	Summary     *string
	Status      string
	ClearTenant bool
}

func (s *Store) GetTenantByCode(ctx context.Context, tenantCode string) (*Tenant, error) {
	row := s.pool.QueryRow(ctx, `
		SELECT id, tenant_code, name, status
		FROM tenants
		WHERE tenant_code = $1
		LIMIT 1
	`, tenantCode)
	var item Tenant
	err := row.Scan(&item.ID, &item.TenantCode, &item.Name, &item.Status)
	if errors.Is(err, pgx.ErrNoRows) {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("get tenant by code: %w", err)
	}
	return &item, nil
}

func (s *Store) ListAdminEvents(ctx context.Context, limit int) ([]AdminEventRecord, error) {
	if limit <= 0 || limit > 200 {
		limit = 50
	}
	rows, err := s.pool.Query(ctx, `
		SELECT
			e.id,
			e.event_public_id,
			e.tenant_id,
			t.tenant_code,
			t.name,
			e.slug,
			e.display_name,
			e.summary,
			e.status,
			e.current_release_id,
			er.release_public_id,
			er.config_label,
			er.manifest_url,
			er.manifest_checksum_sha256,
			er.route_code,
			ep.presentation_public_id,
			ep.name,
			ep.presentation_type,
			cb.content_bundle_public_id,
			cb.name,
			cb.entry_url,
			cb.asset_root_url,
			epc.presentation_public_id,
			epc.name,
			epc.presentation_type,
			cbc.content_bundle_public_id,
			cbc.name,
			cbc.entry_url,
			cbc.asset_root_url,
			mrb.runtime_binding_public_id,
			p.place_public_id,
			ma.map_asset_public_id,
			tr.tile_release_public_id,
			cset.course_set_public_id,
			cv.course_variant_public_id,
			cv.name,
			cv.route_code
		FROM events e
		LEFT JOIN tenants t ON t.id = e.tenant_id
		LEFT JOIN event_releases er ON er.id = e.current_release_id
		LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
		LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
		LEFT JOIN event_presentations epc ON epc.id = e.current_presentation_id
		LEFT JOIN content_bundles cbc ON cbc.id = e.current_content_bundle_id
		LEFT JOIN map_runtime_bindings mrb ON mrb.id = e.current_runtime_binding_id
		LEFT JOIN places p ON p.id = mrb.place_id
		LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
		LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
		LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
		LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
		ORDER BY e.created_at DESC
		LIMIT $1
	`, limit)
	if err != nil {
		return nil, fmt.Errorf("list admin events: %w", err)
	}
	defer rows.Close()

	items := []AdminEventRecord{}
	for rows.Next() {
		item, err := scanAdminEventFromRows(rows)
		if err != nil {
			return nil, err
		}
		items = append(items, *item)
	}
	if err := rows.Err(); err != nil {
		return nil, fmt.Errorf("iterate admin events: %w", err)
	}
	return items, nil
}

func (s *Store) GetAdminEventByPublicID(ctx context.Context, eventPublicID string) (*AdminEventRecord, error) {
	row := s.pool.QueryRow(ctx, `
		SELECT
			e.id,
			e.event_public_id,
			e.tenant_id,
			t.tenant_code,
			t.name,
			e.slug,
			e.display_name,
			e.summary,
			e.status,
			e.current_release_id,
			er.release_public_id,
			er.config_label,
			er.manifest_url,
			er.manifest_checksum_sha256,
			er.route_code,
			ep.presentation_public_id,
			ep.name,
			ep.presentation_type,
			cb.content_bundle_public_id,
			cb.name,
			cb.entry_url,
			cb.asset_root_url,
			epc.presentation_public_id,
			epc.name,
			epc.presentation_type,
			cbc.content_bundle_public_id,
			cbc.name,
			cbc.entry_url,
			cbc.asset_root_url,
			mrb.runtime_binding_public_id,
			p.place_public_id,
			ma.map_asset_public_id,
			tr.tile_release_public_id,
			cset.course_set_public_id,
			cv.course_variant_public_id,
			cv.name,
			cv.route_code
		FROM events e
		LEFT JOIN tenants t ON t.id = e.tenant_id
		LEFT JOIN event_releases er ON er.id = e.current_release_id
		LEFT JOIN event_presentations ep ON ep.id = er.presentation_id
		LEFT JOIN content_bundles cb ON cb.id = er.content_bundle_id
		LEFT JOIN event_presentations epc ON epc.id = e.current_presentation_id
		LEFT JOIN content_bundles cbc ON cbc.id = e.current_content_bundle_id
		LEFT JOIN map_runtime_bindings mrb ON mrb.id = e.current_runtime_binding_id
		LEFT JOIN places p ON p.id = mrb.place_id
		LEFT JOIN map_assets ma ON ma.id = mrb.map_asset_id
		LEFT JOIN tile_releases tr ON tr.id = mrb.tile_release_id
		LEFT JOIN course_sets cset ON cset.id = mrb.course_set_id
		LEFT JOIN course_variants cv ON cv.id = mrb.course_variant_id
		WHERE e.event_public_id = $1
		LIMIT 1
	`, eventPublicID)
	return scanAdminEvent(row)
}

func (s *Store) CreateAdminEvent(ctx context.Context, tx Tx, params CreateAdminEventParams) (*AdminEventRecord, error) {
	row := tx.QueryRow(ctx, `
		INSERT INTO events (tenant_id, event_public_id, slug, display_name, summary, status)
		VALUES ($1, $2, $3, $4, $5, $6)
		RETURNING id, event_public_id, tenant_id, slug, display_name, summary, status, current_release_id
	`, params.TenantID, params.PublicID, params.Slug, params.DisplayName, params.Summary, params.Status)

	var item AdminEventRecord
	if err := row.Scan(
		&item.ID,
		&item.PublicID,
		&item.TenantID,
		&item.Slug,
		&item.DisplayName,
		&item.Summary,
		&item.Status,
		&item.CurrentReleaseID,
	); err != nil {
		return nil, fmt.Errorf("create admin event: %w", err)
	}
	return &item, nil
}

func (s *Store) UpdateAdminEvent(ctx context.Context, tx Tx, params UpdateAdminEventParams) (*AdminEventRecord, error) {
	row := tx.QueryRow(ctx, `
		UPDATE events
		SET tenant_id = CASE WHEN $7 THEN NULL ELSE $2 END,
		    slug = $3,
		    display_name = $4,
		    summary = $5,
		    status = $6
		WHERE id = $1
		RETURNING id, event_public_id, tenant_id, slug, display_name, summary, status, current_release_id
	`, params.EventID, params.TenantID, params.Slug, params.DisplayName, params.Summary, params.Status, params.ClearTenant)

	var item AdminEventRecord
	if err := row.Scan(
		&item.ID,
		&item.PublicID,
		&item.TenantID,
		&item.Slug,
		&item.DisplayName,
		&item.Summary,
		&item.Status,
		&item.CurrentReleaseID,
	); err != nil {
		return nil, fmt.Errorf("update admin event: %w", err)
	}
	return &item, nil
}

func scanAdminEvent(row pgx.Row) (*AdminEventRecord, error) {
	var item AdminEventRecord
	err := row.Scan(
		&item.ID,
		&item.PublicID,
		&item.TenantID,
		&item.TenantCode,
		&item.TenantName,
		&item.Slug,
		&item.DisplayName,
		&item.Summary,
		&item.Status,
		&item.CurrentReleaseID,
		&item.CurrentReleasePubID,
		&item.ConfigLabel,
		&item.ManifestURL,
		&item.ManifestChecksum,
		&item.RouteCode,
		&item.PresentationID,
		&item.PresentationName,
		&item.PresentationType,
		&item.ContentBundleID,
		&item.ContentBundleName,
		&item.ContentEntryURL,
		&item.ContentAssetRootURL,
		&item.CurrentPresentationID,
		&item.CurrentPresentationName,
		&item.CurrentPresentationType,
		&item.CurrentContentBundleID,
		&item.CurrentContentBundleName,
		&item.CurrentContentEntryURL,
		&item.CurrentContentAssetRootURL,
		&item.CurrentRuntimeBindingID,
		&item.CurrentPlaceID,
		&item.CurrentMapAssetID,
		&item.CurrentTileReleaseID,
		&item.CurrentCourseSetID,
		&item.CurrentCourseVariantID,
		&item.CurrentCourseVariantName,
		&item.CurrentRuntimeRouteCode,
	)
	if errors.Is(err, pgx.ErrNoRows) {
		return nil, nil
	}
	if err != nil {
		return nil, fmt.Errorf("scan admin event: %w", err)
	}
	return &item, nil
}

func scanAdminEventFromRows(rows pgx.Rows) (*AdminEventRecord, error) {
	var item AdminEventRecord
	err := rows.Scan(
		&item.ID,
		&item.PublicID,
		&item.TenantID,
		&item.TenantCode,
		&item.TenantName,
		&item.Slug,
		&item.DisplayName,
		&item.Summary,
		&item.Status,
		&item.CurrentReleaseID,
		&item.CurrentReleasePubID,
		&item.ConfigLabel,
		&item.ManifestURL,
		&item.ManifestChecksum,
		&item.RouteCode,
		&item.PresentationID,
		&item.PresentationName,
		&item.PresentationType,
		&item.ContentBundleID,
		&item.ContentBundleName,
		&item.ContentEntryURL,
		&item.ContentAssetRootURL,
		&item.CurrentPresentationID,
		&item.CurrentPresentationName,
		&item.CurrentPresentationType,
		&item.CurrentContentBundleID,
		&item.CurrentContentBundleName,
		&item.CurrentContentEntryURL,
		&item.CurrentContentAssetRootURL,
		&item.CurrentRuntimeBindingID,
		&item.CurrentPlaceID,
		&item.CurrentMapAssetID,
		&item.CurrentTileReleaseID,
		&item.CurrentCourseSetID,
		&item.CurrentCourseVariantID,
		&item.CurrentCourseVariantName,
		&item.CurrentRuntimeRouteCode,
	)
	if err != nil {
		return nil, fmt.Errorf("scan admin event row: %w", err)
	}
	return &item, nil
}
