package drivestore

import (
	"context"
	"errors"
	"fmt"
	"time"

	"github.com/google/uuid"
	"github.com/jackc/pgx/v5"
	"github.com/jackc/pgx/v5/pgxpool"
)

var ErrOrgFolderNotFound = errors.New("org folder not found")
var ErrMountNotFound = errors.New("mount not found")

type OrgFolder struct {
	ID               string    `json:"id"`
	OrgSlug          string    `json:"org_slug"`
	NCFolderID       int       `json:"nc_folder_id"`
	MountPoint       string    `json:"mount_point"`
	QuotaBytes       *int64    `json:"quota_bytes,omitempty"`
	AutoProvisioned  bool      `json:"auto_provisioned"`
	CreatedBy        string    `json:"created_by"`
	CreatedAt        time.Time `json:"created_at"`
	UpdatedAt        time.Time `json:"updated_at"`
}

type Mount struct {
	ID            string    `json:"id"`
	Scope         string    `json:"scope"`
	OwnerUserID   *string   `json:"owner_user_id,omitempty"`
	OrgSlug       *string   `json:"org_slug,omitempty"`
	NCMountID     *int      `json:"nc_mount_id,omitempty"`
	DisplayName   string    `json:"display_name"`
	BackendType   string    `json:"backend_type"`
	MountPoint    string    `json:"mount_point"`
	Status        string    `json:"status"`
	LastError     string    `json:"last_error,omitempty"`
	ConfigEnc     []byte    `json:"-"`
	CreatedAt     time.Time `json:"created_at"`
	UpdatedAt     time.Time `json:"updated_at"`
}

type Store struct {
	db *pgxpool.Pool
}

func NewStore(db *pgxpool.Pool) *Store {
	return &Store{db: db}
}

func (s *Store) ListOrgFolders(ctx context.Context) ([]OrgFolder, error) {
	if s.db == nil {
		return nil, fmt.Errorf("database not configured")
	}
	rows, err := s.db.Query(ctx, `
		SELECT id, org_slug, nc_folder_id, mount_point, quota_bytes,
		       auto_provisioned, created_by, created_at, updated_at
		FROM drive_org_folders
		ORDER BY mount_point ASC
	`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var out []OrgFolder
	for rows.Next() {
		var item OrgFolder
		if err := rows.Scan(
			&item.ID, &item.OrgSlug, &item.NCFolderID, &item.MountPoint,
			&item.QuotaBytes, &item.AutoProvisioned, &item.CreatedBy,
			&item.CreatedAt, &item.UpdatedAt,
		); err != nil {
			return nil, err
		}
		out = append(out, item)
	}
	return out, rows.Err()
}

func (s *Store) GetOrgFolder(ctx context.Context, id string) (OrgFolder, error) {
	if s.db == nil {
		return OrgFolder{}, fmt.Errorf("database not configured")
	}
	var item OrgFolder
	err := s.db.QueryRow(ctx, `
		SELECT id, org_slug, nc_folder_id, mount_point, quota_bytes,
		       auto_provisioned, created_by, created_at, updated_at
		FROM drive_org_folders WHERE id = $1
	`, id).Scan(
		&item.ID, &item.OrgSlug, &item.NCFolderID, &item.MountPoint,
		&item.QuotaBytes, &item.AutoProvisioned, &item.CreatedBy,
		&item.CreatedAt, &item.UpdatedAt,
	)
	if errors.Is(err, pgx.ErrNoRows) {
		return OrgFolder{}, ErrOrgFolderNotFound
	}
	if err != nil {
		return OrgFolder{}, err
	}
	return item, nil
}

func (s *Store) GetOrgFolderBySlug(ctx context.Context, orgSlug string) (OrgFolder, error) {
	if s.db == nil {
		return OrgFolder{}, fmt.Errorf("database not configured")
	}
	var item OrgFolder
	err := s.db.QueryRow(ctx, `
		SELECT id, org_slug, nc_folder_id, mount_point, quota_bytes,
		       auto_provisioned, created_by, created_at, updated_at
		FROM drive_org_folders WHERE org_slug = $1
	`, orgSlug).Scan(
		&item.ID, &item.OrgSlug, &item.NCFolderID, &item.MountPoint,
		&item.QuotaBytes, &item.AutoProvisioned, &item.CreatedBy,
		&item.CreatedAt, &item.UpdatedAt,
	)
	if errors.Is(err, pgx.ErrNoRows) {
		return OrgFolder{}, ErrOrgFolderNotFound
	}
	if err != nil {
		return OrgFolder{}, err
	}
	return item, nil
}

type CreateOrgFolderParams struct {
	OrgSlug         string
	NCFolderID      int
	MountPoint      string
	QuotaBytes      *int64
	AutoProvisioned bool
	CreatedBy       string
}

func (s *Store) CreateOrgFolder(ctx context.Context, p CreateOrgFolderParams) (OrgFolder, error) {
	if s.db == nil {
		return OrgFolder{}, fmt.Errorf("database not configured")
	}
	id := uuid.NewString()
	var item OrgFolder
	err := s.db.QueryRow(ctx, `
		INSERT INTO drive_org_folders (
			id, org_slug, nc_folder_id, mount_point, quota_bytes,
			auto_provisioned, created_by
		) VALUES ($1, $2, $3, $4, $5, $6, $7)
		RETURNING id, org_slug, nc_folder_id, mount_point, quota_bytes,
		          auto_provisioned, created_by, created_at, updated_at
	`, id, p.OrgSlug, p.NCFolderID, p.MountPoint, p.QuotaBytes, p.AutoProvisioned, p.CreatedBy).Scan(
		&item.ID, &item.OrgSlug, &item.NCFolderID, &item.MountPoint,
		&item.QuotaBytes, &item.AutoProvisioned, &item.CreatedBy,
		&item.CreatedAt, &item.UpdatedAt,
	)
	if err != nil {
		return OrgFolder{}, err
	}
	return item, nil
}

func (s *Store) UpdateOrgFolder(ctx context.Context, id, mountPoint string, quotaBytes *int64) (OrgFolder, error) {
	if s.db == nil {
		return OrgFolder{}, fmt.Errorf("database not configured")
	}
	var item OrgFolder
	err := s.db.QueryRow(ctx, `
		UPDATE drive_org_folders
		SET mount_point = $2, quota_bytes = $3, updated_at = NOW()
		WHERE id = $1
		RETURNING id, org_slug, nc_folder_id, mount_point, quota_bytes,
		          auto_provisioned, created_by, created_at, updated_at
	`, id, mountPoint, quotaBytes).Scan(
		&item.ID, &item.OrgSlug, &item.NCFolderID, &item.MountPoint,
		&item.QuotaBytes, &item.AutoProvisioned, &item.CreatedBy,
		&item.CreatedAt, &item.UpdatedAt,
	)
	if errors.Is(err, pgx.ErrNoRows) {
		return OrgFolder{}, ErrOrgFolderNotFound
	}
	if err != nil {
		return OrgFolder{}, err
	}
	return item, nil
}

func (s *Store) DeleteOrgFolder(ctx context.Context, id string) error {
	if s.db == nil {
		return fmt.Errorf("database not configured")
	}
	tag, err := s.db.Exec(ctx, `DELETE FROM drive_org_folders WHERE id = $1`, id)
	if err != nil {
		return err
	}
	if tag.RowsAffected() == 0 {
		return ErrOrgFolderNotFound
	}
	return nil
}

func (s *Store) ListOrgMounts(ctx context.Context) ([]Mount, error) {
	if s.db == nil {
		return nil, fmt.Errorf("database not configured")
	}
	rows, err := s.db.Query(ctx, `
		SELECT id, scope, owner_user_id, org_slug, nc_mount_id, display_name,
		       backend_type, mount_point, status, last_error, created_at, updated_at
		FROM drive_mounts
		WHERE scope = 'org'
		ORDER BY display_name ASC
	`)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanMounts(rows)
}

func (s *Store) ListMountsForUser(ctx context.Context, ownerUserID string, orgSlugs []string) ([]Mount, error) {
	if s.db == nil {
		return nil, fmt.Errorf("database not configured")
	}
	rows, err := s.db.Query(ctx, `
		SELECT id, scope, owner_user_id, org_slug, nc_mount_id, display_name,
		       backend_type, mount_point, status, last_error, created_at, updated_at
		FROM drive_mounts
		WHERE (scope = 'user' AND owner_user_id = $1::uuid)
		   OR (
		     scope = 'org'
		     AND (cardinality($2::text[]) = 0 OR org_slug = ANY($2))
		   )
		ORDER BY display_name ASC
	`, ownerUserID, orgSlugs)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	return scanMounts(rows)
}

func (s *Store) GetMount(ctx context.Context, id string) (Mount, error) {
	if s.db == nil {
		return Mount{}, fmt.Errorf("database not configured")
	}
	row := s.db.QueryRow(ctx, `
		SELECT id, scope, owner_user_id, org_slug, nc_mount_id, display_name,
		       backend_type, mount_point, status, last_error, created_at, updated_at
		FROM drive_mounts WHERE id = $1
	`, id)
	item, err := scanMount(row)
	if errors.Is(err, pgx.ErrNoRows) {
		return Mount{}, ErrMountNotFound
	}
	return item, err
}

type CreateMountParams struct {
	Scope       string
	OwnerUserID *string
	OrgSlug     *string
	NCMountID   *int
	DisplayName string
	BackendType string
	MountPoint  string
	Status      string
	ConfigEnc   []byte
}

func (s *Store) CreateMount(ctx context.Context, p CreateMountParams) (Mount, error) {
	if s.db == nil {
		return Mount{}, fmt.Errorf("database not configured")
	}
	id := uuid.NewString()
	status := p.Status
	if status == "" {
		status = "active"
	}
	row := s.db.QueryRow(ctx, `
		INSERT INTO drive_mounts (
			id, scope, owner_user_id, org_slug, nc_mount_id, display_name,
			backend_type, mount_point, status, config_encrypted
		) VALUES ($1, $2, $3::uuid, $4, $5, $6, $7, $8, $9, $10)
		RETURNING id, scope, owner_user_id, org_slug, nc_mount_id, display_name,
		          backend_type, mount_point, status, last_error, created_at, updated_at
	`, id, p.Scope, p.OwnerUserID, p.OrgSlug, p.NCMountID, p.DisplayName,
		p.BackendType, p.MountPoint, status, p.ConfigEnc)
	return scanMount(row)
}

func (s *Store) UpdateMountConfig(ctx context.Context, id string, configEnc []byte) error {
	if s.db == nil {
		return fmt.Errorf("database not configured")
	}
	tag, err := s.db.Exec(ctx, `
		UPDATE drive_mounts SET config_encrypted = $2, updated_at = NOW() WHERE id = $1
	`, id, configEnc)
	if err != nil {
		return err
	}
	if tag.RowsAffected() == 0 {
		return ErrMountNotFound
	}
	return nil
}

func (s *Store) GetMountConfig(ctx context.Context, id string) ([]byte, error) {
	if s.db == nil {
		return nil, fmt.Errorf("database not configured")
	}
	var config []byte
	err := s.db.QueryRow(ctx, `SELECT config_encrypted FROM drive_mounts WHERE id = $1`, id).Scan(&config)
	if errors.Is(err, pgx.ErrNoRows) {
		return nil, ErrMountNotFound
	}
	return config, err
}

func (s *Store) UpdateMountStatus(ctx context.Context, id, status, lastError string, ncMountID *int) error {
	if s.db == nil {
		return fmt.Errorf("database not configured")
	}
	tag, err := s.db.Exec(ctx, `
		UPDATE drive_mounts
		SET status = $2, last_error = $3, nc_mount_id = COALESCE($4, nc_mount_id), updated_at = NOW()
		WHERE id = $1
	`, id, status, lastError, ncMountID)
	if err != nil {
		return err
	}
	if tag.RowsAffected() == 0 {
		return ErrMountNotFound
	}
	return nil
}

func (s *Store) DeleteMount(ctx context.Context, id string) error {
	if s.db == nil {
		return fmt.Errorf("database not configured")
	}
	tag, err := s.db.Exec(ctx, `DELETE FROM drive_mounts WHERE id = $1`, id)
	if err != nil {
		return err
	}
	if tag.RowsAffected() == 0 {
		return ErrMountNotFound
	}
	return nil
}

type scannable interface {
	Scan(dest ...any) error
}

func scanMount(row scannable) (Mount, error) {
	var item Mount
	err := row.Scan(
		&item.ID, &item.Scope, &item.OwnerUserID, &item.OrgSlug, &item.NCMountID,
		&item.DisplayName, &item.BackendType, &item.MountPoint, &item.Status,
		&item.LastError, &item.CreatedAt, &item.UpdatedAt,
	)
	return item, err
}

func scanMounts(rows pgx.Rows) ([]Mount, error) {
	var out []Mount
	for rows.Next() {
		item, err := scanMount(rows)
		if err != nil {
			return nil, err
		}
		out = append(out, item)
	}
	return out, rows.Err()
}
