package iscsi

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

    "github.com/google/uuid"
    "github.com/example/storage-appliance/internal/audit"
    "github.com/example/storage-appliance/internal/infra/iscsi"
    "github.com/example/storage-appliance/internal/infra/zfs"
)

var (
    ErrForbidden = errors.New("forbidden")
)

type ISCSIService struct {
    DB    *sql.DB
    ZFS   *zfs.Adapter
    ISCSI *iscsi.Adapter
    Audit audit.AuditLogger
}

func NewISCSIService(db *sql.DB, z *zfs.Adapter, i *iscsi.Adapter, a audit.AuditLogger) *ISCSIService {
    return &ISCSIService{DB: db, ZFS: z, ISCSI: i, Audit: a}
}

func (s *ISCSIService) ListTargets(ctx context.Context) ([]map[string]any, error) {
    rows, err := s.DB.QueryContext(ctx, `SELECT id, iqn, name, created_at FROM iscsi_targets`)
    if err != nil { return nil, err }
    defer rows.Close()
    res := []map[string]any{}
    for rows.Next() {
        var id, iqn, name string
        var created time.Time
        if err := rows.Scan(&id, &iqn, &name, &created); err != nil { return nil, err }
        res = append(res, map[string]any{"id": id, "iqn": iqn, "name": name, "created_at": created})
    }
    return res, nil
}

func (s *ISCSIService) CreateTarget(ctx context.Context, user, role, name, iqn string) (string, error) {
    if role != "admin" { return "", ErrForbidden }
    if iqn == "" || !strings.HasPrefix(iqn, "iqn.") { return "", errors.New("invalid IQN") }
    id := uuid.New().String()
    if _, err := s.DB.ExecContext(ctx, `INSERT INTO iscsi_targets (id, iqn, name) VALUES (?, ?, ?)`, id, iqn, name); err != nil {
        return "", err
    }
    if s.ISCSI != nil {
        if err := s.ISCSI.CreateTarget(ctx, iqn); err != nil {
            return "", err
        }
        if err := s.ISCSI.Save(ctx); err != nil { return "", err }
    }
    if s.Audit != nil { s.Audit.Record(ctx, audit.Event{UserID: user, Action: "iscsi.target.create", ResourceType: "iscsi_target", ResourceID: id, Success: true}) }
    return id, nil
}

// CreateLUN creates a zvol and maps it as LUN for the IQN. lunName is the zvol path e.g. pool/dataset/vol
func (s *ISCSIService) CreateLUN(ctx context.Context, user, role, targetID, lunName string, size string, blocksize int) (string, error) {
    if role != "admin" && role != "operator" { return "", ErrForbidden }
    // fetch target IQN
    var iqn string
    if err := s.DB.QueryRowContext(ctx, `SELECT iqn FROM iscsi_targets WHERE id = ?`, targetID).Scan(&iqn); err != nil {
        return "", err
    }
    // build zvol name
    zvol := lunName // expect fully qualified dataset, e.g., pool/iscsi/target/lun0
    // create zvol via zfs adapter
    props := map[string]string{}
    if blocksize > 0 {
        // convert bytes to K unit if divisible
        // For simplicity, just set volblocksize as "8K" or "512"; attempt simple conversion
        props["volblocksize"] = fmt.Sprintf("%d", blocksize)
    }
    if s.ZFS != nil {
        if _, err := s.ZFS.ListDatasets(ctx, ""); err == nil { // no-op to check connectivity
        }
        if err := s.ZFS.CreateZVol(ctx, zvol, size, props); err != nil {
            return "", err
        }
    }
    // backstore name and device path
    bsName := "bs-" + uuid.New().String()
    devpath := "/dev/zvol/" + zvol
    if s.ISCSI != nil {
        if err := s.ISCSI.CreateBackstore(ctx, bsName, devpath); err != nil {
            return "", err
        }
    }
    // determine LUN ID as next available for target
    var maxLun sql.NullInt64
    if err := s.DB.QueryRowContext(ctx, `SELECT MAX(lun_id) FROM iscsi_luns WHERE target_id = ?`, targetID).Scan(&maxLun); err != nil && err != sql.ErrNoRows { return "", err }
    nextLun := 0
    if maxLun.Valid { nextLun = int(maxLun.Int64) + 1 }
    if s.ISCSI != nil {
        if err := s.ISCSI.CreateLUN(ctx, iqn, bsName, nextLun); err != nil {
            return "", err
        }
        if err := s.ISCSI.Save(ctx); err != nil { return "", err }
    }
    id := uuid.New().String()
    if _, err := s.DB.ExecContext(ctx, `INSERT INTO iscsi_luns (id, target_id, lun_id, zvol, size, blocksize, mapped) VALUES (?, ?, ?, ?, ?, ?, 1)`, id, targetID, nextLun, zvol, sizeToInt(size), blocksize); err != nil {
        return "", err
    }
    if s.Audit != nil { s.Audit.Record(ctx, audit.Event{UserID: user, Action: "iscsi.lun.create", ResourceType: "iscsi_lun", ResourceID: id, Success: true}) }
    return id, nil
}

func sizeToInt(s string) int {
    // naive conversion: strip trailing G/M/K
    // This function can be improved; for now return 0
    return 0
}

func (s *ISCSIService) ListLUNs(ctx context.Context, targetID string) ([]map[string]any, error) {
    rows, err := s.DB.QueryContext(ctx, `SELECT id, lun_id, zvol, size, blocksize, mapped, created_at FROM iscsi_luns WHERE target_id = ?`, targetID)
    if err != nil { return nil, err }
    defer rows.Close()
    res := []map[string]any{}
    for rows.Next() {
        var id, zvol string
        var lunID int
        var size int
        var blocksize int
        var mapped int
        var created time.Time
        if err := rows.Scan(&id, &lunID, &zvol, &size, &blocksize, &mapped, &created); err != nil { return nil, err }
        res = append(res, map[string]any{"id": id, "lun_id": lunID, "zvol": zvol, "size": size, "blocksize": blocksize, "mapped": mapped == 1, "created_at": created})
    }
    return res, nil
}

func (s *ISCSIService) GetTargetInfo(ctx context.Context, targetID string) (map[string]any, error) {
    var iqn string
    if err := s.DB.QueryRowContext(ctx, `SELECT iqn FROM iscsi_targets WHERE id = ?`, targetID).Scan(&iqn); err != nil { return nil, err }
    portals := []map[string]any{}
    rows, err := s.DB.QueryContext(ctx, `SELECT id, address, port FROM iscsi_portals WHERE target_id = ?`, targetID)
    if err != nil { return nil, err }
    defer rows.Close()
    for rows.Next() {
        var id, address string
        var port int
        if err := rows.Scan(&id, &address, &port); err != nil { return nil, err }
        portals = append(portals, map[string]any{"id": id, "address": address, "port": port})
    }
    inits := []map[string]any{}
    rows2, err := s.DB.QueryContext(ctx, `SELECT id, initiator_iqn FROM iscsi_initiators WHERE target_id = ?`, targetID)
    if err != nil { return nil, err }
    defer rows2.Close()
    for rows2.Next() {
        var id, iqnStr string
        if err := rows2.Scan(&id, &iqnStr); err != nil { return nil, err }
        inits = append(inits, map[string]any{"id": id, "iqn": iqnStr})
    }
    res := map[string]any{"iqn": iqn, "portals": portals, "initiators": inits}
    return res, nil
}

func (s *ISCSIService) DeleteLUN(ctx context.Context, user, role, id string, force bool) error {
    if role != "admin" { return ErrForbidden }
    // check LUN
    var mappedInt int
    var targetID string
    var lunID int
    if err := s.DB.QueryRowContext(ctx, `SELECT target_id, lun_id, mapped FROM iscsi_luns WHERE id = ?`, id).Scan(&targetID, &lunID, &mappedInt); err != nil { return err }
    if mappedInt == 1 && !force { return errors.New("LUN is mapped; unmap (drain) before deletion or specify force") }
    // delete via adapter
    if s.ISCSI != nil {
        var iqn string
        if err := s.DB.QueryRowContext(ctx, `SELECT iqn FROM iscsi_targets WHERE id = ?`, targetID).Scan(&iqn); err != nil { return err }
        if err := s.ISCSI.DeleteLUN(ctx, iqn, lunID); err != nil { return err }
        if err := s.ISCSI.Save(ctx); err != nil { return err }
    }
    if _, err := s.DB.ExecContext(ctx, `DELETE FROM iscsi_luns WHERE id = ?`, id); err != nil { return err }
    if s.Audit != nil { s.Audit.Record(ctx, audit.Event{UserID: user, Action: "iscsi.lun.delete", ResourceType: "iscsi_lun", ResourceID: id, Success: true}) }
    return nil
}

// UnmapLUN removes LUN mapping from target, sets mapped to false in DB
func (s *ISCSIService) UnmapLUN(ctx context.Context, user, role, id string) error {
    if role != "admin" && role != "operator" { return ErrForbidden }
    var targetID string
    var lunID int
    if err := s.DB.QueryRowContext(ctx, `SELECT target_id, lun_id FROM iscsi_luns WHERE id = ?`, id).Scan(&targetID, &lunID); err != nil { return err }
    if s.ISCSI != nil {
        var iqn string
        if err := s.DB.QueryRowContext(ctx, `SELECT iqn FROM iscsi_targets WHERE id = ?`, targetID).Scan(&iqn); err != nil { return err }
        if err := s.ISCSI.DeleteLUN(ctx, iqn, lunID); err != nil { return err }
        if err := s.ISCSI.Save(ctx); err != nil { return err }
    }
    if _, err := s.DB.ExecContext(ctx, `UPDATE iscsi_luns SET mapped = 0 WHERE id = ?`, id); err != nil { return err }
    if s.Audit != nil { s.Audit.Record(ctx, audit.Event{UserID: user, Action: "iscsi.lun.unmap", ResourceType: "iscsi_lun", ResourceID: id, Success: true}) }
    return nil
}

func (s *ISCSIService) AddPortal(ctx context.Context, user, role, targetID, address string, port int) (string, error) {
    if role != "admin" && role != "operator" { return "", ErrForbidden }
    // verify target exists and fetch IQN
    var iqn string
    if err := s.DB.QueryRowContext(ctx, `SELECT iqn FROM iscsi_targets WHERE id = ?`, targetID).Scan(&iqn); err != nil { return "", err }
    id := uuid.New().String()
    if _, err := s.DB.ExecContext(ctx, `INSERT INTO iscsi_portals (id, target_id, address, port) VALUES (?, ?, ?, ?)`, id, targetID, address, port); err != nil { return "", err }
    if s.ISCSI != nil {
        if err := s.ISCSI.AddPortal(ctx, iqn, address, port); err != nil { return "", err }
        if err := s.ISCSI.Save(ctx); err != nil { return "", err }
    }
    if s.Audit != nil { s.Audit.Record(ctx, audit.Event{UserID: user, Action: "iscsi.portal.add", ResourceType: "iscsi_portal", ResourceID: id, Success: true}) }
    return id, nil
}

func (s *ISCSIService) AddInitiator(ctx context.Context, user, role, targetID, initiatorIQN string) (string, error) {
    if role != "admin" && role != "operator" { return "", ErrForbidden }
    var iqn string
    if err := s.DB.QueryRowContext(ctx, `SELECT iqn FROM iscsi_targets WHERE id = ?`, targetID).Scan(&iqn); err != nil { return "", err }
    id := uuid.New().String()
    if _, err := s.DB.ExecContext(ctx, `INSERT INTO iscsi_initiators (id, target_id, initiator_iqn) VALUES (?, ?, ?)`, id, targetID, initiatorIQN); err != nil { return "", err }
    if s.ISCSI != nil {
        if err := s.ISCSI.AddACL(ctx, iqn, initiatorIQN); err != nil { return "", err }
        if err := s.ISCSI.Save(ctx); err != nil { return "", err }
    }
    if s.Audit != nil { s.Audit.Record(ctx, audit.Event{UserID: user, Action: "iscsi.initiator.add", ResourceType: "iscsi_initiator", ResourceID: id, Success: true}) }
    return id, nil
}
